From c0dad2ef161eebb8db1fca975affe104ff71b844 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Fri, 7 Jul 2023 14:37:08 +0200 Subject: [PATCH 0001/2851] add nushell dependency analysis script --- util/deps.nu | 155 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 155 insertions(+) create mode 100644 util/deps.nu diff --git a/util/deps.nu b/util/deps.nu new file mode 100644 index 00000000000..a5bd94c8f51 --- /dev/null +++ b/util/deps.nu @@ -0,0 +1,155 @@ +# This is a script to analyze the dependencies of this project. +# It is a replacement of / complement to +# +# - cargo tree (used by this script) +# - cargo deps +# - cargo deny +# +# The idea is that by calling all_dep_info, you get a table of all dependencies +# in Cargo.lock, with a few additional columns based on some other tools. +# Currently, these tools are +# +# - cargo tree +# - the crates.io API +# +# The most useful columns in the table are: +# +# - `name`: the name of the crate. +# - `version`: the version of the crate. +# - `num_versions`: the number of versions in Cargo.lock. +# - `normal_dep`: whether the crate is a normal dependency. +# - `build_dep`: whether the crate is a build dependency. +# - `dev_dep`: whether the crate is a dev dependency. +# - `organisation`: the GitHub/GitLab organisation or user of the repository of the crate. +# - `repository_name`: the name of the repository the crate is in. The format is "{owner}/{repo}". +# - `dependencies`: direct dependencies of the crate (in the format of Cargo.lock). +# +# To use this script, start nushell (tested only on version 0.82.0), import the library and +# call `all_dep_info`: +# +# ``` +# > nu +# > use util/deps.nu +# > let dep = (deps all_dep_info) +# ``` +# +# Then you can perform analysis. For example, to group the dependencies by organisation: +# +# ``` +# > $dep | group-by organisation +# ``` +# +# Or to find all crates with multiple versions (like cargo deny): +# ``` +# > $dep | where versions > 1 +# ``` +# +# Ideas to expand this: +# +# - Figure out the whole dependency graph +# - Figure out which platforms and which features enable which crates +# - Figure out which utils require which crates +# - Count the number of crates on different platforms +# - Add license information +# - Add functions to perform common analyses +# - Add info from cargo bloat +# - Add MSRV info +# - Add up-to-date info (the necessary info is there, it just needs to be derived) +# - Check the number of owners/contributors +# - Make a webpage to more easily explore the data + +# Read the packages a Cargo.lock file +def read_lockfile [name: path] { + open $name | from toml | get package +} + +# Read the names output by cargo tree +export def read_tree_names [edges: string, features: string] { + cargo tree -e $edges --features $features + | rg "[a-zA-Z0-9_-]+ v[0-9.]+" -o + | lines + | each {|x| parse_name_and_version $x } +} + +def parse_name_and_version [s: string] { + let s = ($s | split row " ") + + let name = $s.0 + let version = if ($s | length) > 1 { + $s.1 | str substring 1.. + } else { + "" + } + + {name: $name, version: $version} +} + +# Read the crates.io info for a list of crates names +def read_crates_io [names: list] { + let total = ($names | length) + $names | enumerate | par-each {|el| + let key = $el.index + let name = $el.item + print $"($key)/($total): ($name)" + http get $"https://crates.io/api/v1/crates/($name)" | get crate + } +} + +def in_table [col_name, table] { + insert $col_name {|el| + $table + | any {|table_el| + $table_el.name == $el.name and $table_el.version == $el.version } + } +} + +# Add column for a dependency type +def add_dep_type [dep_type: string, features: string] { + in_table $"($dep_type)_dep" (read_tree_names $dep_type $features) +} + +export def all_dep_info [] { + let features = unix,feat_selinux + + let lock = (read_lockfile Cargo.lock) + + $lock + # Add number of versions + | join ($lock | group-by name | transpose | update column1 { length } | rename name num_versions) name + # Add dependency types + | add_dep_type normal $features + | add_dep_type build $features + | add_dep_type dev $features + | insert used {|x| $x.normal_dep or $x.build_dep or $x.dev_dep} + # Add crates.io info + | join (read_crates_io ($lock.name | uniq)) name + # Add GH org or user info + # The organisation is an indicator that crates should be treated as one dependency. + # However, there are also unrelated projects by a single organisation, so it's not + # clear. + | insert organisation {|x| + let repository = $x.repository? + if ($repository == null) { "" } else { + $repository | url parse | get path | path split | get 1 + } + } + # Add repository (truncate everything after repo name) + # If we get a url like + # https://github.com/uutils/coreutils/tree/src/uu/ls + # we only keep + # uutils/coreutils + # The idea is that crates in the same repo definitely belong to the same project and should + # be treated as one dependency. + | insert repository_name {|x| + let repository = $x.repository? + if ($repository == null) { '' } else { + $repository + | url parse + | get path + | path split + | select 1 2 + | path join + } + } +} + From 948836fb8ff099af1fb78fbf38f315ac52001142 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Sun, 9 Jul 2023 16:43:48 +0200 Subject: [PATCH 0002/2851] Update util/deps.nu Co-authored-by: Daniel Hofstetter --- util/deps.nu | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util/deps.nu b/util/deps.nu index a5bd94c8f51..a35e2d98c96 100644 --- a/util/deps.nu +++ b/util/deps.nu @@ -41,7 +41,7 @@ # # Or to find all crates with multiple versions (like cargo deny): # ``` -# > $dep | where versions > 1 +# > $dep | where num_versions > 1 # ``` # # Ideas to expand this: From d345a280bf588b325babb29c60dc7d84d2c22832 Mon Sep 17 00:00:00 2001 From: John Shin Date: Sun, 30 Jul 2023 14:37:17 -0700 Subject: [PATCH 0003/2851] seq: update inf and nan parsing --- src/uu/seq/src/numberparse.rs | 40 +++++++++++++++++------------------ 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/src/uu/seq/src/numberparse.rs b/src/uu/seq/src/numberparse.rs index 23d94ea2b79..1921e2e4c4e 100644 --- a/src/uu/seq/src/numberparse.rs +++ b/src/uu/seq/src/numberparse.rs @@ -69,27 +69,25 @@ fn parse_no_decimal_no_exponent(s: &str) -> Result { // Possibly "NaN" or "inf". - // - // TODO In Rust v1.53.0, this change - // https://github.com/rust-lang/rust/pull/78618 improves the - // parsing of floats to include being able to parse "NaN" - // and "inf". So when the minimum version of this crate is - // increased to 1.53.0, we should just use the built-in - // `f32` parsing instead. - if s.eq_ignore_ascii_case("inf") { - Ok(PreciseNumber::new( - Number::Float(ExtendedBigDecimal::Infinity), - 0, - 0, - )) - } else if s.eq_ignore_ascii_case("-inf") { - Ok(PreciseNumber::new( - Number::Float(ExtendedBigDecimal::MinusInfinity), - 0, - 0, - )) - } else if s.eq_ignore_ascii_case("nan") || s.eq_ignore_ascii_case("-nan") { - Err(ParseNumberError::Nan) + if let Ok(num) = f32::from_str(s) { + // pattern matching on floating point literal is not encouraged 'https://github.com/rust-lang/rust/issues/41620' + if num == f32::INFINITY { + Ok(PreciseNumber::new( + Number::Float(ExtendedBigDecimal::Infinity), + 0, + 0, + )) + } else if num == f32::NEG_INFINITY { + Ok(PreciseNumber::new( + Number::Float(ExtendedBigDecimal::MinusInfinity), + 0, + 0, + )) + } else if num.is_nan() { + Err(ParseNumberError::Nan) + } else { + Err(ParseNumberError::Float) + } } else { Err(ParseNumberError::Float) } From a3e68d5bbd3d063cac01f77e9870121791e24012 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Wed, 2 Aug 2023 23:57:26 +0200 Subject: [PATCH 0004/2851] uucore: start work on a completely new printf implementation --- src/uucore/Cargo.toml | 2 +- src/uucore/src/lib/features.rs | 6 +- src/uucore/src/lib/features/format/mod.rs | 144 +++++ src/uucore/src/lib/features/format/spec.rs | 523 ++++++++++++++++++ src/uucore/src/lib/features/memo.rs | 175 ------ src/uucore/src/lib/features/tokenize/mod.rs | 5 - .../tokenize/num_format/format_field.rs | 43 -- .../features/tokenize/num_format/formatter.rs | 59 -- .../num_format/formatters/base_conv/mod.rs | 270 --------- .../num_format/formatters/base_conv/tests.rs | 56 -- .../formatters/cninetyninehexfloatf.rs | 115 ---- .../tokenize/num_format/formatters/decf.rs | 185 ------- .../num_format/formatters/float_common.rs | 377 ------------- .../tokenize/num_format/formatters/floatf.rs | 43 -- .../tokenize/num_format/formatters/intf.rs | 282 ---------- .../tokenize/num_format/formatters/mod.rs | 9 - .../tokenize/num_format/formatters/scif.rs | 43 -- .../lib/features/tokenize/num_format/mod.rs | 4 - .../tokenize/num_format/num_format.rs | 271 --------- src/uucore/src/lib/features/tokenize/sub.rs | 452 --------------- src/uucore/src/lib/features/tokenize/token.rs | 39 -- .../lib/features/tokenize/unescaped_text.rs | 279 ---------- src/uucore/src/lib/lib.rs | 4 +- 23 files changed, 672 insertions(+), 2714 deletions(-) create mode 100644 src/uucore/src/lib/features/format/mod.rs create mode 100644 src/uucore/src/lib/features/format/spec.rs delete mode 100644 src/uucore/src/lib/features/memo.rs delete mode 100644 src/uucore/src/lib/features/tokenize/mod.rs delete mode 100644 src/uucore/src/lib/features/tokenize/num_format/format_field.rs delete mode 100644 src/uucore/src/lib/features/tokenize/num_format/formatter.rs delete mode 100644 src/uucore/src/lib/features/tokenize/num_format/formatters/base_conv/mod.rs delete mode 100644 src/uucore/src/lib/features/tokenize/num_format/formatters/base_conv/tests.rs delete mode 100644 src/uucore/src/lib/features/tokenize/num_format/formatters/cninetyninehexfloatf.rs delete mode 100644 src/uucore/src/lib/features/tokenize/num_format/formatters/decf.rs delete mode 100644 src/uucore/src/lib/features/tokenize/num_format/formatters/float_common.rs delete mode 100644 src/uucore/src/lib/features/tokenize/num_format/formatters/floatf.rs delete mode 100644 src/uucore/src/lib/features/tokenize/num_format/formatters/intf.rs delete mode 100644 src/uucore/src/lib/features/tokenize/num_format/formatters/mod.rs delete mode 100644 src/uucore/src/lib/features/tokenize/num_format/formatters/scif.rs delete mode 100644 src/uucore/src/lib/features/tokenize/num_format/mod.rs delete mode 100644 src/uucore/src/lib/features/tokenize/num_format/num_format.rs delete mode 100644 src/uucore/src/lib/features/tokenize/sub.rs delete mode 100644 src/uucore/src/lib/features/tokenize/token.rs delete mode 100644 src/uucore/src/lib/features/tokenize/unescaped_text.rs diff --git a/src/uucore/Cargo.toml b/src/uucore/Cargo.toml index d376e807a20..1c1d4c75443 100644 --- a/src/uucore/Cargo.toml +++ b/src/uucore/Cargo.toml @@ -76,7 +76,7 @@ entries = ["libc"] fs = ["libc", "winapi-util", "windows-sys"] fsext = ["libc", "time", "windows-sys"] lines = [] -memo = ["itertools"] +format = ["itertools"] mode = ["libc"] perms = ["libc", "walkdir"] process = ["libc"] diff --git a/src/uucore/src/lib/features.rs b/src/uucore/src/lib/features.rs index f8a8d2d10df..fe48399877b 100644 --- a/src/uucore/src/lib/features.rs +++ b/src/uucore/src/lib/features.rs @@ -8,14 +8,12 @@ pub mod fs; pub mod fsext; #[cfg(feature = "lines")] pub mod lines; -#[cfg(feature = "memo")] -pub mod memo; +#[cfg(feature = "format")] +pub mod format; #[cfg(feature = "ringbuffer")] pub mod ringbuffer; #[cfg(feature = "sum")] pub mod sum; -#[cfg(feature = "memo")] -mod tokenize; // * (platform-specific) feature-gated modules // ** non-windows (i.e. Unix + Fuchsia) diff --git a/src/uucore/src/lib/features/format/mod.rs b/src/uucore/src/lib/features/format/mod.rs new file mode 100644 index 00000000000..abd92011cc4 --- /dev/null +++ b/src/uucore/src/lib/features/format/mod.rs @@ -0,0 +1,144 @@ +//! Main entry point for our implementation of printf. +//! +//! The [`printf`] and [`sprintf`] closely match the behavior of the +//! corresponding C functions: the former renders a formatted string +//! to stdout, the latter renders to a new [`String`] object. +//! +//! In addition to the [`printf`] and [`sprintf`] functions, we expose the +//! [`Format`] struct, which represents a parsed format string. This reduces +//! the need for parsing a format string multiple times and assures that no +//! parsing errors occur during writing. +// spell-checker:ignore (vars) charf decf floatf intf scif strf Cninety + +// mod num_format; +mod spec; + +use spec::Spec; +use std::io::{stdout, Write}; + +pub enum FormatError { + SpecError, + IoError(std::io::Error), + NoMoreArguments, + InvalidArgument(FormatArgument), +} + +/// A single item to format +enum FormatItem { + /// A format specifier + Spec(Spec), + /// Some plain text + Text(Vec), + /// A single character + /// + /// Added in addition to `Text` as an optimization. + Char(u8), +} + +pub enum FormatArgument { + Char(char), + String(String), + UnsignedInt(u64), + SignedInt(i64), + Float(f64), +} + +impl FormatItem { + fn write<'a>(&self, mut writer: impl Write, args: &mut impl Iterator) -> Result<(), FormatError> { + match self { + FormatItem::Spec(spec) => spec.write(writer, args), + FormatItem::Text(bytes) => writer.write_all(bytes).map_err(FormatError::IoError), + FormatItem::Char(char) => writer.write_all(&[*char]).map_err(FormatError::IoError), + } + } +} + +fn parse_iter(fmt: &[u8]) -> impl Iterator> + '_ { + let mut rest = fmt; + std::iter::from_fn(move || { + if rest.is_empty() { + return None; + } + + match rest.iter().position(|c| *c == b'%') { + None => { + let final_text = rest; + rest = &[]; + Some(Ok(FormatItem::Text(final_text.into()))) + } + Some(0) => { + // Handle the spec + rest = &rest[1..]; + match rest.get(0) { + None => Some(Ok(FormatItem::Char(b'%'))), + Some(b'%') => { + rest = &rest[1..]; + Some(Ok(FormatItem::Char(b'%'))) + } + Some(_) => { + let spec = match Spec::parse(&mut rest) { + Some(spec) => spec, + None => return Some(Err(FormatError::SpecError)), + }; + Some(Ok(FormatItem::Spec(spec))) + } + } + } + Some(i) => { + // The `after` slice includes the % so it will be handled correctly + // in the next iteration. + let (before, after) = rest.split_at(i); + rest = after; + return Some(Ok(FormatItem::Text(before.into()))); + } + } + }) +} + +/// Write a formatted string to stdout. +/// +/// `format_string` contains the template and `args` contains the +/// arguments to render into the template. +/// +/// See also [`sprintf`], which creates a new formatted [`String`]. +/// +/// # Examples +/// +/// ```rust +/// use uucore::format::printf; +/// +/// printf("hello %s", &["world".to_string()]).unwrap(); +/// // prints "hello world" +/// ``` +pub fn printf(format_string: &[u8], arguments: impl IntoIterator) -> Result<(), FormatError> { + printf_writer(stdout(), format_string, arguments) +} + +fn printf_writer(mut writer: impl Write, format_string: &[u8], args: impl IntoIterator) -> Result<(), FormatError> { + let mut args = args.into_iter(); + for item in parse_iter(format_string) { + item?.write(&mut writer, &mut args)?; + } + Ok(()) +} + +/// Create a new formatted string. +/// +/// `format_string` contains the template and `args` contains the +/// arguments to render into the template. +/// +/// See also [`printf`], which prints to stdout. +/// +/// # Examples +/// +/// ```rust +/// use uucore::format::sprintf; +/// +/// let s = sprintf("hello %s", &["world".to_string()]).unwrap(); +/// assert_eq!(s, "hello world".to_string()); +/// ``` +pub fn sprintf(format_string: &[u8], arguments: impl IntoIterator) -> Result, FormatError> { + let mut writer = Vec::new(); + printf_writer(&mut writer, format_string, arguments)?; + Ok(writer) +} diff --git a/src/uucore/src/lib/features/format/spec.rs b/src/uucore/src/lib/features/format/spec.rs new file mode 100644 index 00000000000..4319e44d93f --- /dev/null +++ b/src/uucore/src/lib/features/format/spec.rs @@ -0,0 +1,523 @@ +// spell-checker:ignore (vars) charf decf floatf intf scif strf Cninety + +use super::{FormatArgument, FormatError}; +use std::{fmt::Display, io::Write}; + +pub enum Spec { + Char { + width: Option>, + align_left: bool, + }, + String { + width: Option>, + align_left: bool, + }, + SignedInt { + width: Option>, + positive_sign: PositiveSign, + alignment: NumberAlignment, + }, + UnsignedInt { + variant: UnsignedIntVariant, + width: Option>, + alignment: NumberAlignment, + }, + Float { + variant: FloatVariant, + case: Case, + force_decimal: ForceDecimal, + width: Option>, + positive_sign: PositiveSign, + alignment: NumberAlignment, + precision: Option>, + }, +} + +#[derive(Clone, Copy)] +pub enum UnsignedIntVariant { + Decimal, + Octal(Prefix), + Hexadecimal(Case, Prefix), +} + +#[derive(Clone, Copy)] + +pub enum FloatVariant { + Decimal, + Scientific, + Shortest, + Hexadecimal, +} + +#[derive(Clone, Copy, PartialEq, Eq)] +pub enum Case { + Lowercase, + Uppercase, +} + +#[derive(Clone, Copy, PartialEq, Eq)] +pub enum Prefix { + No, + Yes, +} + +#[derive(Clone, Copy, PartialEq, Eq)] +pub enum ForceDecimal { + No, + Yes, +} + +#[derive(Clone, Copy)] +pub enum PositiveSign { + None, + Plus, + Space, +} + +#[derive(Clone, Copy)] +pub enum NumberAlignment { + Left, + RightSpace, + RightZero, +} + +/// Precision and width specified might use an asterisk to indicate that they are +/// determined by an argument. +#[derive(Clone, Copy)] +pub enum CanAsterisk { + Fixed(T), + Asterisk, +} + +/// Size of the expected type (ignored) +/// +/// We ignore this parameter entirely, but we do parse it. +/// It could be used in the future if the need arises. +enum Length { + /// signed/unsigned char ("hh") + Char, + /// signed/unsigned short int ("h") + Short, + /// signed/unsigned long int ("l") + Long, + /// signed/unsigned long long int ("ll") + LongLong, + /// intmax_t ("j") + IntMaxT, + /// size_t ("z") + SizeT, + /// ptrdiff_t ("t") + PtfDiffT, + /// long double ("L") + LongDouble, +} + +impl Spec { + pub fn parse(rest: &mut &[u8]) -> Option { + // Based on the C++ reference, the spec format looks like: + // + // %[flags][width][.precision][length]specifier + // + // However, we have already parsed the '%'. + + let mut minus = false; + let mut plus = false; + let mut space = false; + let mut hash = false; + let mut zero = false; + + while let Some(x @ (b'-' | b'+' | b' ' | b'#' | b'0')) = rest.get(0) { + match x { + b'-' => minus = true, + b'+' => plus = true, + b' ' => space = true, + b'#' => hash = true, + b'0' => zero = true, + _ => unreachable!(), + } + *rest = &rest[1..] + } + + let width = eat_asterisk_or_number(rest); + + let precision = if let Some(b'.') = rest.get(0) { + Some(eat_asterisk_or_number(rest).unwrap_or(CanAsterisk::Fixed(0))) + } else { + None + }; + + let length = rest.get(0).and_then(|c| { + Some(match c { + b'h' => { + if let Some(b'h') = rest.get(1) { + *rest = &rest[1..]; + Length::Char + } else { + Length::Short + } + } + b'l' => { + if let Some(b'l') = rest.get(1) { + *rest = &rest[1..]; + Length::Long + } else { + Length::LongLong + } + } + b'j' => Length::IntMaxT, + b'z' => Length::SizeT, + b't' => Length::PtfDiffT, + b'L' => Length::LongDouble, + _ => return None, + }) + }); + + if length.is_some() { + *rest = &rest[1..]; + } + + Some(match rest.get(0)? { + b'c' => Spec::Char { + width, + align_left: minus, + }, + b's' => Spec::String { + width, + align_left: minus, + }, + b'd' | b'i' => Spec::SignedInt { + width, + alignment: match (minus, zero) { + (true, _) => NumberAlignment::Left, + (false, true) => NumberAlignment::RightZero, + (false, false) => NumberAlignment::RightSpace, + }, + positive_sign: match (plus, space) { + (true, _) => PositiveSign::Plus, + (false, true) => PositiveSign::Space, + (false, false) => PositiveSign::None, + }, + }, + c @ (b'u' | b'o' | b'x' | b'X') => { + let prefix = match hash { + false => Prefix::No, + true => Prefix::Yes, + }; + let alignment = match (minus, zero) { + (true, _) => NumberAlignment::Left, + (false, true) => NumberAlignment::RightZero, + (false, false) => NumberAlignment::RightSpace, + }; + let variant = match c { + b'u' => UnsignedIntVariant::Decimal, + b'o' => UnsignedIntVariant::Octal(prefix), + b'x' => UnsignedIntVariant::Hexadecimal(Case::Lowercase, prefix), + b'X' => UnsignedIntVariant::Hexadecimal(Case::Uppercase, prefix), + _ => unreachable!(), + }; + Spec::UnsignedInt { + variant, + width, + alignment, + } + } + c @ (b'f' | b'F' | b'e' | b'E' | b'g' | b'G' | b'a' | b'A') => Spec::Float { + width, + precision, + variant: match c { + b'f' | b'F' => FloatVariant::Decimal, + b'e' | b'E' => FloatVariant::Scientific, + b'g' | b'G' => FloatVariant::Shortest, + b'a' | b'A' => FloatVariant::Hexadecimal, + _ => unreachable!(), + }, + force_decimal: match hash { + false => ForceDecimal::No, + true => ForceDecimal::Yes, + }, + case: match c.is_ascii_uppercase() { + false => Case::Lowercase, + true => Case::Uppercase, + }, + alignment: match (minus, zero) { + (true, _) => NumberAlignment::Left, + (false, true) => NumberAlignment::RightZero, + (false, false) => NumberAlignment::RightSpace, + }, + positive_sign: match (plus, space) { + (true, _) => PositiveSign::Plus, + (false, true) => PositiveSign::Space, + (false, false) => PositiveSign::None, + }, + }, + _ => return None, + }) + } + + pub fn write<'a>( + &self, + mut writer: impl Write, + mut args: impl Iterator, + ) -> Result<(), FormatError> { + match self { + &Spec::Char { width, align_left } => { + let width = resolve_asterisk(width, &mut args)?.unwrap_or(0); + let arg = next_arg(&mut args)?; + match arg { + FormatArgument::Char(c) => write_padded(writer, c, width, false, align_left), + _ => Err(FormatError::InvalidArgument(arg)), + } + } + &Spec::String { width, align_left } => { + let width = resolve_asterisk(width, &mut args)?.unwrap_or(0); + let arg = next_arg(&mut args)?; + match arg { + FormatArgument::String(s) => write_padded(writer, s, width, false, align_left), + _ => Err(FormatError::InvalidArgument(arg)), + } + } + &Spec::SignedInt { + width, + positive_sign, + alignment, + } => { + let width = resolve_asterisk(width, &mut args)?.unwrap_or(0); + + let arg = next_arg(&mut args)?; + let FormatArgument::SignedInt(i) = arg else { + return Err(FormatError::InvalidArgument(arg)); + }; + + if i >= 0 { + match positive_sign { + PositiveSign::None => Ok(()), + PositiveSign::Plus => write!(writer, "+"), + PositiveSign::Space => write!(writer, " "), + } + .map_err(FormatError::IoError)?; + } + + match alignment { + NumberAlignment::Left => write!(writer, "{i: write!(writer, "{i:>width$}"), + NumberAlignment::RightZero => write!(writer, "{i:0>width$}"), + } + .map_err(FormatError::IoError) + } + &Spec::UnsignedInt { + variant, + width, + alignment, + } => { + let width = resolve_asterisk(width, &mut args)?.unwrap_or(0); + + let arg = next_arg(args)?; + let FormatArgument::SignedInt(i) = arg else { + return Err(FormatError::InvalidArgument(arg)); + }; + + let s = match variant { + UnsignedIntVariant::Decimal => format!("{i}"), + UnsignedIntVariant::Octal(Prefix::No) => format!("{i:o}"), + UnsignedIntVariant::Octal(Prefix::Yes) => format!("{i:#o}"), + UnsignedIntVariant::Hexadecimal(Case::Lowercase, Prefix::No) => { + format!("{i:x}") + } + UnsignedIntVariant::Hexadecimal(Case::Lowercase, Prefix::Yes) => { + format!("{i:#x}") + } + UnsignedIntVariant::Hexadecimal(Case::Uppercase, Prefix::No) => { + format!("{i:X}") + } + UnsignedIntVariant::Hexadecimal(Case::Uppercase, Prefix::Yes) => { + format!("{i:#X}") + } + }; + + match alignment { + NumberAlignment::Left => write!(writer, "{s: write!(writer, "{s:>width$}"), + NumberAlignment::RightZero => write!(writer, "{s:0>width$}"), + } + .map_err(FormatError::IoError) + } + &Spec::Float { + variant, + case, + force_decimal, + width, + positive_sign, + alignment, + precision, + } => { + let width = resolve_asterisk(width, &mut args)?.unwrap_or(0); + let precision = resolve_asterisk(precision, &mut args)?.unwrap_or(6); + + let arg = next_arg(args)?; + let FormatArgument::Float(f) = arg else { + return Err(FormatError::InvalidArgument(arg)); + }; + + match positive_sign { + PositiveSign::None => Ok(()), + PositiveSign::Plus => write!(writer, "+"), + PositiveSign::Space => write!(writer, " "), + } + .map_err(FormatError::IoError)?; + + let s = match variant { + FloatVariant::Decimal => format_float_decimal(f, precision, case, force_decimal), + FloatVariant::Scientific => { + format_float_scientific(f, precision, case, force_decimal) + } + FloatVariant::Shortest => format_float_shortest(f, precision, case, force_decimal), + FloatVariant::Hexadecimal => todo!(), + }; + + match alignment { + NumberAlignment::Left => write!(writer, "{s: write!(writer, "{s:>width$}"), + NumberAlignment::RightZero => write!(writer, "{s:0>width$}"), + } + .map_err(FormatError::IoError) + } + } + } +} + +fn format_float_decimal( + f: f64, + precision: usize, + case: Case, + force_decimal: ForceDecimal, +) -> String { + if !f.is_finite() { + let mut s = format!("{f}"); + if case == Case::Lowercase { + s.make_ascii_uppercase(); + } + return s; + } + + if precision == 0 && force_decimal == ForceDecimal::Yes { + format!("{f:.0}.") + } else { + format!("{f:.*}", precision) + } +} + +fn format_float_scientific( + f: f64, + precision: usize, + case: Case, + force_decimal: ForceDecimal, +) -> String { + // If the float is NaN, -Nan, Inf or -Inf, format like any other float + if !f.is_finite() { + let mut s = format!("{f}"); + if case == Case::Lowercase { + s.make_ascii_uppercase(); + } + return s; + } + + let exponent: i32 = f.log10().floor() as i32; + let normalized = f / 10.0_f64.powi(exponent); + + let additional_dot = if precision == 0 && ForceDecimal::Yes == force_decimal { + "." + } else { + "" + }; + + let exp_char = match case { + Case::Lowercase => 'e', + Case::Uppercase => 'E', + }; + + format!( + "{normalized:.*}{additional_dot}{exp_char}{exponent:+03}", + precision + ) +} + +// TODO: This could be optimized. It's not terribly important though. +fn format_float_shortest( + f: f64, + precision: usize, + case: Case, + force_decimal: ForceDecimal, +) -> String { + let a = format_float_decimal(f, precision, case, force_decimal); + let b = format_float_scientific(f, precision, case, force_decimal); + + if a.len() > b.len() { + b + } else { + a + } +} + +fn resolve_asterisk( + option: Option>, + args: impl Iterator, +) -> Result, FormatError> { + Ok(match option { + None => None, + Some(CanAsterisk::Asterisk) => { + let arg = next_arg(args)?; + match arg { + FormatArgument::UnsignedInt(u) => match usize::try_from(u) { + Ok(u) => Some(u), + Err(_) => return Err(FormatError::InvalidArgument(arg)), + }, + _ => return Err(FormatError::InvalidArgument(arg)), + } + } + Some(CanAsterisk::Fixed(w)) => Some(w), + }) +} + +fn next_arg( + mut arguments: impl Iterator, +) -> Result { + arguments.next().ok_or(FormatError::NoMoreArguments) +} + +fn write_padded( + mut writer: impl Write, + text: impl Display, + width: usize, + pad_zero: bool, + left: bool, +) -> Result<(), FormatError> { + match (left, pad_zero) { + (false, false) => write!(writer, "{text: >width$}"), + (false, true) => write!(writer, "{text:0>width$}"), + // 0 is ignored if we pad left. + (true, _) => write!(writer, "{text: Option> { + if let Some(b'*') = rest.get(0) { + *rest = &rest[1..]; + Some(CanAsterisk::Asterisk) + } else { + eat_number(rest).map(CanAsterisk::Fixed) + } +} + +fn eat_number(rest: &mut &[u8]) -> Option { + match rest.iter().position(|b| !b.is_ascii_digit()) { + None | Some(0) => None, + Some(i) => { + // TODO: This might need to handle errors better + // For example in case of overflow. + let parsed = std::str::from_utf8(&rest[..i]).unwrap().parse().unwrap(); + *rest = &rest[i..]; + Some(parsed) + } + } +} diff --git a/src/uucore/src/lib/features/memo.rs b/src/uucore/src/lib/features/memo.rs deleted file mode 100644 index 47d04f5b861..00000000000 --- a/src/uucore/src/lib/features/memo.rs +++ /dev/null @@ -1,175 +0,0 @@ -//! Main entry point for our implementation of printf. -//! -//! The [`printf`] and [`sprintf`] closely match the behavior of the -//! corresponding C functions: the former renders a formatted string -//! to stdout, the latter renders to a new [`String`] object. -use crate::display::Quotable; -use crate::error::{UResult, USimpleError}; -use crate::features::tokenize::sub::SubParser; -use crate::features::tokenize::token::Token; -use crate::features::tokenize::unescaped_text::UnescapedText; -use crate::show_warning; -use itertools::put_back_n; -use std::io::{stdout, Cursor, Write}; -use std::iter::Peekable; -use std::slice::Iter; - -/// Memo runner of printf -/// Takes a format string and arguments -/// 1. tokenize format string into tokens, consuming -/// any subst. arguments along the way. -/// 2. feeds remaining arguments into function -/// that prints tokens. -struct Memo { - tokens: Vec, -} - -fn warn_excess_args(first_arg: &str) { - show_warning!( - "ignoring excess arguments, starting with {}", - first_arg.quote() - ); -} - -impl Memo { - fn new( - writer: &mut W, - pf_string: &str, - pf_args_it: &mut Peekable>, - ) -> UResult - where - W: Write, - { - let mut pm = Self { tokens: Vec::new() }; - let mut it = put_back_n(pf_string.chars()); - let mut has_sub = false; - loop { - if let Some(x) = UnescapedText::from_it_core(writer, &mut it, false) { - pm.tokens.push(x); - } - if let Some(x) = SubParser::from_it(writer, &mut it, pf_args_it)? { - if !has_sub { - has_sub = true; - } - pm.tokens.push(x); - } - if let Some(x) = it.next() { - it.put_back(x); - } else { - break; - } - } - if !has_sub { - let mut drain = false; - if let Some(first_arg) = pf_args_it.peek() { - warn_excess_args(first_arg); - drain = true; - } - if drain { - loop { - // drain remaining args; - if pf_args_it.next().is_none() { - break; - } - } - } - } - Ok(pm) - } - fn apply(&self, writer: &mut W, pf_args_it: &mut Peekable>) - where - W: Write, - { - for tkn in &self.tokens { - tkn.write(writer, pf_args_it); - } - } - fn run_all(writer: &mut W, pf_string: &str, pf_args: &[String]) -> UResult<()> - where - W: Write, - { - let mut arg_it = pf_args.iter().peekable(); - let pm = Self::new(writer, pf_string, &mut arg_it)?; - loop { - if arg_it.peek().is_none() { - return Ok(()); - } - pm.apply(writer, &mut arg_it); - } - } -} - -/// Write a formatted string to stdout. -/// -/// `format_string` contains the template and `args` contains the -/// arguments to render into the template. -/// -/// See also [`sprintf`], which creates a new formatted [`String`]. -/// -/// # Examples -/// -/// ```rust -/// use uucore::memo::printf; -/// -/// printf("hello %s", &["world".to_string()]).unwrap(); -/// // prints "hello world" -/// ``` -pub fn printf(format_string: &str, args: &[String]) -> UResult<()> { - let mut writer = stdout(); - Memo::run_all(&mut writer, format_string, args) -} - -/// Create a new formatted string. -/// -/// `format_string` contains the template and `args` contains the -/// arguments to render into the template. -/// -/// See also [`printf`], which prints to stdout. -/// -/// # Examples -/// -/// ```rust -/// use uucore::memo::sprintf; -/// -/// let s = sprintf("hello %s", &["world".to_string()]).unwrap(); -/// assert_eq!(s, "hello world".to_string()); -/// ``` -pub fn sprintf(format_string: &str, args: &[String]) -> UResult { - let mut writer = Cursor::new(vec![]); - Memo::run_all(&mut writer, format_string, args)?; - let buf = writer.into_inner(); - match String::from_utf8(buf) { - Ok(s) => Ok(s), - Err(e) => Err(USimpleError::new( - 1, - format!("failed to parse formatted string as UTF-8: {e}"), - )), - } -} - -#[cfg(test)] -mod tests { - - use crate::memo::sprintf; - - #[test] - fn test_sprintf_smoke() { - assert_eq!(sprintf("", &[]).unwrap(), "".to_string()); - } - - #[test] - fn test_sprintf_no_args() { - assert_eq!( - sprintf("hello world", &[]).unwrap(), - "hello world".to_string() - ); - } - - #[test] - fn test_sprintf_string() { - assert_eq!( - sprintf("hello %s", &["world".to_string()]).unwrap(), - "hello world".to_string() - ); - } -} diff --git a/src/uucore/src/lib/features/tokenize/mod.rs b/src/uucore/src/lib/features/tokenize/mod.rs deleted file mode 100644 index dfe44a0e56b..00000000000 --- a/src/uucore/src/lib/features/tokenize/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -#[allow(clippy::module_inception)] -mod num_format; -pub mod sub; -pub mod token; -pub mod unescaped_text; diff --git a/src/uucore/src/lib/features/tokenize/num_format/format_field.rs b/src/uucore/src/lib/features/tokenize/num_format/format_field.rs deleted file mode 100644 index 02998cde540..00000000000 --- a/src/uucore/src/lib/features/tokenize/num_format/format_field.rs +++ /dev/null @@ -1,43 +0,0 @@ -// spell-checker:ignore (vars) charf decf floatf intf scif strf Cninety - -//! Primitives used by Sub Tokenizer -//! and num_format modules -#[derive(Clone)] -pub enum FieldType { - Strf, - Floatf, - CninetyNineHexFloatf, - Scif, - Decf, - Intf, - Charf, -} - -// #[allow(non_camel_case_types)] -// pub enum FChar { -// d, -// e, -// E, -// i, -// f, -// F, -// g, -// G, -// u, -// x, -// X, -// o -// } -// - -// a Sub Tokens' fields are stored -// as a single object so they can be more simply -// passed by ref to num_format in a Sub method -#[derive(Clone)] -pub struct FormatField<'a> { - pub min_width: Option, - pub second_field: Option, - pub field_char: &'a char, - pub field_type: &'a FieldType, - pub orig: &'a String, -} diff --git a/src/uucore/src/lib/features/tokenize/num_format/formatter.rs b/src/uucore/src/lib/features/tokenize/num_format/formatter.rs deleted file mode 100644 index ed7d5a0f609..00000000000 --- a/src/uucore/src/lib/features/tokenize/num_format/formatter.rs +++ /dev/null @@ -1,59 +0,0 @@ -//! Primitives used by num_format and sub_modules. -//! never dealt with above (e.g. Sub Tokenizer never uses these) - -use crate::{display::Quotable, show_error}; -use itertools::{put_back_n, PutBackN}; -use std::str::Chars; - -use super::format_field::FormatField; - -// contains the rough ingredients to final -// output for a number, organized together -// to allow for easy generalization of output manipulation -// (e.g. max number of digits after decimal) -#[derive(Default)] -pub struct FormatPrimitive { - pub prefix: Option, - pub pre_decimal: Option, - pub post_decimal: Option, - pub suffix: Option, -} - -#[derive(Clone, PartialEq, Eq)] -pub enum Base { - Ten = 10, - Hex = 16, - Octal = 8, -} - -// information from the beginning of a numeric argument -// the precedes the beginning of a numeric value -pub struct InitialPrefix { - pub radix_in: Base, - pub sign: i8, - pub offset: usize, -} - -pub trait Formatter { - // return a FormatPrimitive for - // particular field char(s), given the argument - // string and prefix information (sign, radix) - fn get_primitive( - &self, - field: &FormatField, - in_prefix: &InitialPrefix, - str_in: &str, - ) -> Option; - // return a string from a FormatPrimitive, - // given information about the field - fn primitive_to_str(&self, prim: &FormatPrimitive, field: FormatField) -> String; -} -pub fn get_it_at(offset: usize, str_in: &str) -> PutBackN { - put_back_n(str_in[offset..].chars()) -} - -// TODO: put this somewhere better -pub fn warn_incomplete_conv(pf_arg: &str) { - // important: keep println here not print - show_error!("{}: value not completely converted", pf_arg.maybe_quote()); -} diff --git a/src/uucore/src/lib/features/tokenize/num_format/formatters/base_conv/mod.rs b/src/uucore/src/lib/features/tokenize/num_format/formatters/base_conv/mod.rs deleted file mode 100644 index 3df9f7129bc..00000000000 --- a/src/uucore/src/lib/features/tokenize/num_format/formatters/base_conv/mod.rs +++ /dev/null @@ -1,270 +0,0 @@ -// spell-checker:ignore (ToDO) arrnum arr_num mult basenum bufferval refd vals arrfloat conv intermed addl - -pub fn arrnum_int_mult(arr_num: &[u8], basenum: u8, base_ten_int_fact: u8) -> Vec { - let mut carry: u16 = 0; - let mut rem: u16; - let mut new_amount: u16; - let fact: u16 = u16::from(base_ten_int_fact); - let base: u16 = u16::from(basenum); - - let mut ret_rev: Vec = Vec::new(); - let mut it = arr_num.iter().rev(); - loop { - let i = it.next(); - match i { - Some(u) => { - new_amount = (u16::from(*u) * fact) + carry; - rem = new_amount % base; - carry = (new_amount - rem) / base; - ret_rev.push(rem as u8); - } - None => { - while carry != 0 { - rem = carry % base; - carry = (carry - rem) / base; - ret_rev.push(rem as u8); - } - break; - } - } - } - let ret: Vec = ret_rev.into_iter().rev().collect(); - ret -} - -#[allow(dead_code)] -pub struct Remainder<'a> { - pub position: usize, - pub replace: Vec, - pub arr_num: &'a Vec, -} - -#[allow(dead_code)] -pub struct DivOut<'a> { - pub quotient: u8, - pub remainder: Remainder<'a>, -} - -#[allow(dead_code)] -pub fn arrnum_int_div_step<'a>( - rem_in: &'a Remainder, - radix_in: u8, - base_ten_int_divisor: u8, - after_decimal: bool, -) -> DivOut<'a> { - let mut rem_out = Remainder { - position: rem_in.position, - replace: Vec::new(), - arr_num: rem_in.arr_num, - }; - - let mut bufferval: u16 = 0; - let base: u16 = u16::from(radix_in); - let divisor: u16 = u16::from(base_ten_int_divisor); - let mut traversed = 0; - - let mut quotient = 0; - let refd_vals = &rem_in.arr_num[rem_in.position + rem_in.replace.len()..]; - let mut it_replace = rem_in.replace.iter(); - let mut it_f = refd_vals.iter(); - loop { - let u = match it_replace.next() { - Some(u_rep) => u16::from(*u_rep), - None => match it_f.next() { - Some(u_orig) => u16::from(*u_orig), - None => { - if !after_decimal { - break; - } - 0 - } - }, - }; - traversed += 1; - bufferval += u; - if bufferval > divisor { - while bufferval >= divisor { - quotient += 1; - bufferval -= divisor; - } - rem_out.replace = if bufferval == 0 { - Vec::new() - } else { - let remainder_as_arrnum = unsigned_to_arrnum(bufferval); - base_conv_vec(&remainder_as_arrnum, 10, radix_in) - }; - rem_out.position += 1 + (traversed - rem_out.replace.len()); - break; - } else { - bufferval *= base; - } - } - DivOut { - quotient, - remainder: rem_out, - } -} -pub fn arrnum_int_add(arrnum: &[u8], basenum: u8, base_ten_int_term: u8) -> Vec { - let mut carry: u16 = u16::from(base_ten_int_term); - let mut rem: u16; - let mut new_amount: u16; - let base: u16 = u16::from(basenum); - - let mut ret_rev: Vec = Vec::new(); - let mut it = arrnum.iter().rev(); - loop { - let i = it.next(); - match i { - Some(u) => { - new_amount = u16::from(*u) + carry; - rem = new_amount % base; - carry = (new_amount - rem) / base; - ret_rev.push(rem as u8); - } - None => { - while carry != 0 { - rem = carry % base; - carry = (carry - rem) / base; - ret_rev.push(rem as u8); - } - break; - } - } - } - let ret: Vec = ret_rev.into_iter().rev().collect(); - ret -} - -pub fn base_conv_vec(src: &[u8], radix_src: u8, radix_dest: u8) -> Vec { - let mut result = vec![0]; - for i in src { - result = arrnum_int_mult(&result, radix_dest, radix_src); - result = arrnum_int_add(&result, radix_dest, *i); - } - result -} - -#[allow(dead_code)] -pub fn unsigned_to_arrnum(src: u16) -> Vec { - let mut result: Vec = Vec::new(); - let mut src_tmp: u16 = src; - while src_tmp > 0 { - result.push((src_tmp % 10) as u8); - src_tmp /= 10; - } - result.reverse(); - result -} - -// temporary needs-improvement-function -pub fn base_conv_float(src: &[u8], radix_src: u8, _radix_dest: u8) -> f64 { - // it would require a lot of addl code - // to implement this for arbitrary string input. - // until then, the below operates as an outline - // of how it would work. - let mut factor: f64 = 1_f64; - let radix_src_float: f64 = f64::from(radix_src); - let mut r: f64 = 0_f64; - for (i, u) in src.iter().enumerate() { - if i > 15 { - break; - } - factor /= radix_src_float; - r += factor * f64::from(*u); - } - r -} - -pub fn str_to_arrnum(src: &str, radix_def_src: &dyn RadixDef) -> Vec { - let mut intermed_in: Vec = Vec::new(); - for c in src.chars() { - #[allow(clippy::single_match)] - match radix_def_src.parse_char(c) { - Some(u) => { - intermed_in.push(u); - } - None => {} //todo err msg on incorrect - } - } - intermed_in -} - -pub fn arrnum_to_str(src: &[u8], radix_def_dest: &dyn RadixDef) -> String { - let mut str_out = String::new(); - for u in src.iter() { - #[allow(clippy::single_match)] - match radix_def_dest.format_u8(*u) { - Some(c) => { - str_out.push(c); - } - None => {} //todo - } - } - str_out -} - -pub fn base_conv_str( - src: &str, - radix_def_src: &dyn RadixDef, - radix_def_dest: &dyn RadixDef, -) -> String { - let intermed_in: Vec = str_to_arrnum(src, radix_def_src); - let intermed_out = base_conv_vec( - &intermed_in, - radix_def_src.get_max(), - radix_def_dest.get_max(), - ); - arrnum_to_str(&intermed_out, radix_def_dest) -} - -pub trait RadixDef { - fn get_max(&self) -> u8; - fn parse_char(&self, x: char) -> Option; - fn format_u8(&self, x: u8) -> Option; -} -pub struct RadixTen; - -const ZERO_ASC: u8 = b'0'; -const UPPER_A_ASC: u8 = b'A'; -const LOWER_A_ASC: u8 = b'a'; - -impl RadixDef for RadixTen { - fn get_max(&self) -> u8 { - 10 - } - fn parse_char(&self, c: char) -> Option { - match c { - '0'..='9' => Some(c as u8 - ZERO_ASC), - _ => None, - } - } - fn format_u8(&self, u: u8) -> Option { - match u { - 0..=9 => Some((ZERO_ASC + u) as char), - _ => None, - } - } -} -pub struct RadixHex; -impl RadixDef for RadixHex { - fn get_max(&self) -> u8 { - 16 - } - fn parse_char(&self, c: char) -> Option { - match c { - '0'..='9' => Some(c as u8 - ZERO_ASC), - 'A'..='F' => Some(c as u8 + 10 - UPPER_A_ASC), - 'a'..='f' => Some(c as u8 + 10 - LOWER_A_ASC), - _ => None, - } - } - fn format_u8(&self, u: u8) -> Option { - match u { - 0..=9 => Some((ZERO_ASC + u) as char), - 10..=15 => Some((UPPER_A_ASC + (u - 10)) as char), - _ => None, - } - } -} - -mod tests; diff --git a/src/uucore/src/lib/features/tokenize/num_format/formatters/base_conv/tests.rs b/src/uucore/src/lib/features/tokenize/num_format/formatters/base_conv/tests.rs deleted file mode 100644 index 903a3faf142..00000000000 --- a/src/uucore/src/lib/features/tokenize/num_format/formatters/base_conv/tests.rs +++ /dev/null @@ -1,56 +0,0 @@ -// spell-checker:ignore (ToDO) arrnum mult - -#[cfg(test)] -use super::*; - -#[test] -fn test_arrnum_int_mult() { - // (in base 10) 12 * 4 = 48 - let factor: Vec = vec![1, 2]; - let base_num = 10; - let base_ten_int_fact: u8 = 4; - let should_output: Vec = vec![4, 8]; - - let product = arrnum_int_mult(&factor, base_num, base_ten_int_fact); - assert!(product == should_output); -} - -#[test] -fn test_arrnum_int_non_base_10() { - // (in base 3) - // 5 * 4 = 20 - let factor: Vec = vec![1, 2]; - let base_num = 3; - let base_ten_int_fact: u8 = 4; - let should_output: Vec = vec![2, 0, 2]; - - let product = arrnum_int_mult(&factor, base_num, base_ten_int_fact); - assert!(product == should_output); -} - -#[test] -fn test_arrnum_int_div_short_circuit() { - // ( - let arrnum: Vec = vec![5, 5, 5, 5, 0]; - let base_num = 10; - let base_ten_int_divisor: u8 = 41; - let remainder_passed_in = Remainder { - position: 1, - replace: vec![1, 3], - arr_num: &arrnum, - }; - - // the "replace" should mean the number being divided - // is 1350, the first time you can get 41 to go into - // 1350, its at 135, where you can get a quotient of - // 3 and a remainder of 12; - - let quotient_should_be: u8 = 3; - let remainder_position_should_be: usize = 3; - let remainder_replace_should_be = vec![1, 2]; - - let result = arrnum_int_div_step(&remainder_passed_in, base_num, base_ten_int_divisor, false); - assert!(quotient_should_be == result.quotient); - assert!(remainder_position_should_be == result.remainder.position); - assert!(remainder_replace_should_be == result.remainder.replace); -} diff --git a/src/uucore/src/lib/features/tokenize/num_format/formatters/cninetyninehexfloatf.rs b/src/uucore/src/lib/features/tokenize/num_format/formatters/cninetyninehexfloatf.rs deleted file mode 100644 index a5c51153efd..00000000000 --- a/src/uucore/src/lib/features/tokenize/num_format/formatters/cninetyninehexfloatf.rs +++ /dev/null @@ -1,115 +0,0 @@ -// spell-checker:ignore (vars) charf decf floatf intf scif strf Cninety -// spell-checker:ignore (ToDO) arrnum - -//! formatter for %a %F C99 Hex-floating-point subs -use super::super::format_field::FormatField; -use super::super::formatter::{FormatPrimitive, Formatter, InitialPrefix}; -use super::base_conv; -use super::base_conv::RadixDef; -use super::float_common::{primitive_to_str_common, FloatAnalysis}; - -#[derive(Default)] -pub struct CninetyNineHexFloatf { - #[allow(dead_code)] - as_num: f64, -} -impl CninetyNineHexFloatf { - pub fn new() -> Self { - Self::default() - } -} - -impl Formatter for CninetyNineHexFloatf { - fn get_primitive( - &self, - field: &FormatField, - initial_prefix: &InitialPrefix, - str_in: &str, - ) -> Option { - let second_field = field.second_field.unwrap_or(6) + 1; - let analysis = FloatAnalysis::analyze( - str_in, - initial_prefix, - Some(second_field as usize), - None, - true, - ); - let f = get_primitive_hex( - initial_prefix, - &str_in[initial_prefix.offset..], - &analysis, - second_field as usize, - *field.field_char == 'A', - ); - Some(f) - } - fn primitive_to_str(&self, prim: &FormatPrimitive, field: FormatField) -> String { - primitive_to_str_common(prim, &field) - } -} - -// c99 hex has unique requirements of all floating point subs in pretty much every part of building a primitive, from prefix and suffix to need for base conversion (in all other cases if you don't have decimal you must have decimal, here it's the other way around) - -// on the todo list is to have a trait for get_primitive that is implemented by each float formatter and can override a default. when that happens we can take the parts of get_primitive_dec specific to dec and spin them out to their own functions that can be overridden. -fn get_primitive_hex( - initial_prefix: &InitialPrefix, - _str_in: &str, - _analysis: &FloatAnalysis, - _last_dec_place: usize, - capitalized: bool, -) -> FormatPrimitive { - let prefix = Some(String::from(if initial_prefix.sign == -1 { - "-0x" - } else { - "0x" - })); - - // TODO actual conversion, make sure to get back mantissa. - // for hex to hex, it's really just a matter of moving the - // decimal point and calculating the mantissa by its initial - // position and its moves, with every position counting for - // the addition or subtraction of 4 (2**4, because 4 bits in a hex digit) - // to the exponent. - // decimal's going to be a little more complicated. correct simulation - // of glibc will require after-decimal division to a specified precision. - // the difficult part of this (arrnum_int_div_step) is already implemented. - - // the hex float name may be a bit misleading in terms of how to go about the - // conversion. The best way to do it is to just convert the float number - // directly to base 2 and then at the end translate back to hex. - let mantissa = 0; - let suffix = Some({ - let ind = if capitalized { "P" } else { "p" }; - if mantissa >= 0 { - format!("{ind}+{mantissa}") - } else { - format!("{ind}{mantissa}") - } - }); - FormatPrimitive { - prefix, - suffix, - ..Default::default() - } -} - -#[allow(dead_code)] -fn to_hex(src: &str, before_decimal: bool) -> String { - let radix_ten = base_conv::RadixTen; - let radix_hex = base_conv::RadixHex; - if before_decimal { - base_conv::base_conv_str(src, &radix_ten, &radix_hex) - } else { - let as_arrnum_ten = base_conv::str_to_arrnum(src, &radix_ten); - let s = format!( - "{}", - base_conv::base_conv_float(&as_arrnum_ten, radix_ten.get_max(), radix_hex.get_max()) - ); - if s.len() > 2 { - String::from(&s[2..]) - } else { - // zero - s - } - } -} diff --git a/src/uucore/src/lib/features/tokenize/num_format/formatters/decf.rs b/src/uucore/src/lib/features/tokenize/num_format/formatters/decf.rs deleted file mode 100644 index 2ee53882e5d..00000000000 --- a/src/uucore/src/lib/features/tokenize/num_format/formatters/decf.rs +++ /dev/null @@ -1,185 +0,0 @@ -// spell-checker:ignore (vars) charf decf floatf intf scif strf Cninety - -//! formatter for %g %G decimal subs -use super::super::format_field::FormatField; -use super::super::formatter::{FormatPrimitive, Formatter, InitialPrefix}; -use super::float_common::{get_primitive_dec, primitive_to_str_common, FloatAnalysis}; - -const SIGNIFICANT_FIGURES: usize = 6; - -// Parse a numeric string as the nearest integer with a given significance. -// This is a helper function for round(). -// Examples: -// round_to_significance("456", 1) == 500 -// round_to_significance("456", 2) == 460 -// round_to_significance("456", 9) == 456 -fn round_to_significance(input: &str, significant_figures: usize) -> u32 { - if significant_figures < input.len() { - // If the input has too many digits, use a float intermediary - // to round it before converting to an integer. Otherwise, - // converting straight to integer will truncate. - // There might be a cleaner way to do this... - let digits = &input[..significant_figures + 1]; - let float_representation = digits.parse::().unwrap(); - (float_representation / 10.0).round() as u32 - } else { - input.parse::().unwrap_or(0) - } -} - -// Removing trailing zeroes, expressing the result as an integer where -// possible. This is a helper function for round(). -fn truncate(mut format: FormatPrimitive) -> FormatPrimitive { - if let Some(ref post_dec) = format.post_decimal { - let trimmed = post_dec.trim_end_matches('0'); - - if trimmed.is_empty() { - // If there are no nonzero digits after the decimal point, - // use integer formatting by clearing post_decimal and suffix. - format.post_decimal = Some(String::new()); - if format.suffix == Some("e+00".into()) { - format.suffix = Some(String::new()); - } - } else if trimmed.len() != post_dec.len() { - // Otherwise, update the format to remove only the trailing - // zeroes (e.g. "4.50" becomes "4.5", not "4"). If there were - // no trailing zeroes, do nothing. - format.post_decimal = Some(trimmed.to_owned()); - } - } - format -} - -// Round a format to six significant figures and remove trailing zeroes. -fn round(mut format: FormatPrimitive) -> FormatPrimitive { - let mut significant_digits_remaining = SIGNIFICANT_FIGURES; - - // First, take as many significant digits as possible from pre_decimal, - if format.pre_decimal.is_some() { - let input = format.pre_decimal.as_ref().unwrap(); - let rounded = round_to_significance(input, significant_digits_remaining); - let mut rounded_str = rounded.to_string(); - significant_digits_remaining -= rounded_str.len(); - - // If the pre_decimal has exactly enough significant digits, - // round the input to the nearest integer. If the first - // post_decimal digit is 5 or higher, round up by incrementing - // the pre_decimal number. Otherwise, use the pre_decimal as-is. - if significant_digits_remaining == 0 { - if let Some(digits) = &format.post_decimal { - if digits.chars().next().unwrap_or('0') >= '5' { - let rounded = rounded + 1; - rounded_str = rounded.to_string(); - } - } - } - format.pre_decimal = Some(rounded_str); - } - - // If no significant digits remain, or there's no post_decimal to - // round, return the rounded pre_decimal value with no post_decimal. - // Otherwise, round the post_decimal to the remaining significance. - if significant_digits_remaining == 0 { - format.post_decimal = Some(String::new()); - } else if let Some(input) = format.post_decimal { - let leading_zeroes = input.len() - input.trim_start_matches('0').len(); - let digits = &input[leading_zeroes..]; - - // In the post_decimal, leading zeroes are significant. "01.0010" - // has one significant digit in pre_decimal, and 3 from post_decimal. - let mut post_decimal_str = String::with_capacity(significant_digits_remaining); - for _ in 0..leading_zeroes { - post_decimal_str.push('0'); - } - - if leading_zeroes < significant_digits_remaining { - // After significant leading zeroes, round the remaining digits - // to any remaining significance. - let rounded = round_to_significance(digits, significant_digits_remaining); - post_decimal_str.push_str(&rounded.to_string()); - } else if leading_zeroes == significant_digits_remaining - && digits.chars().next().unwrap_or('0') >= '5' - { - // If necessary, round up the post_decimal ("1.000009" should - // round to 1.00001, instead of truncating after the last - // significant leading zero). - post_decimal_str.pop(); - post_decimal_str.push('1'); - } else { - // If the rounded post_decimal is entirely zeroes, discard - // it and use integer formatting instead. - post_decimal_str = String::new(); - } - - format.post_decimal = Some(post_decimal_str); - } - truncate(format) -} - -// Given an exponent used in scientific notation, return whether the -// number is small enough to be expressed as a decimal instead. "Small -// enough" is based only on the number's magnitude, not the length of -// any string representation. -fn should_represent_as_decimal(suffix: &Option) -> bool { - match suffix { - Some(exponent) => { - if exponent.chars().nth(1) == Some('-') { - exponent < &"e-05".into() - } else { - exponent < &"e+06".into() - } - } - None => true, - } -} - -pub struct Decf; - -impl Decf { - pub fn new() -> Self { - Self - } -} -impl Formatter for Decf { - fn get_primitive( - &self, - field: &FormatField, - initial_prefix: &InitialPrefix, - str_in: &str, - ) -> Option { - let second_field = field.second_field.unwrap_or(6) + 1; - // default to scif interpretation so as to not truncate input vals - // (that would be displayed in scif) based on relation to decimal place - let analysis = FloatAnalysis::analyze( - str_in, - initial_prefix, - Some(second_field as usize + 1), - None, - false, - ); - let mut f_dec = get_primitive_dec( - initial_prefix, - &str_in[initial_prefix.offset..], - &analysis, - second_field as usize, - Some(*field.field_char == 'G'), - ); - - if should_represent_as_decimal(&f_dec.suffix) { - // Use decimal formatting instead of scientific notation - // if the input's magnitude is small. - f_dec = get_primitive_dec( - initial_prefix, - &str_in[initial_prefix.offset..], - &analysis, - second_field as usize, - None, - ); - } - - Some(round(f_dec)) - } - fn primitive_to_str(&self, prim: &FormatPrimitive, field: FormatField) -> String { - primitive_to_str_common(prim, &field) - } -} diff --git a/src/uucore/src/lib/features/tokenize/num_format/formatters/float_common.rs b/src/uucore/src/lib/features/tokenize/num_format/formatters/float_common.rs deleted file mode 100644 index e0a29217c4a..00000000000 --- a/src/uucore/src/lib/features/tokenize/num_format/formatters/float_common.rs +++ /dev/null @@ -1,377 +0,0 @@ -// spell-checker:ignore (vars) charf decf floatf intf scif strf Cninety -// spell-checker:ignore (ToDO) arrnum - -use super::super::format_field::FormatField; -use super::super::formatter::{ - get_it_at, warn_incomplete_conv, Base, FormatPrimitive, InitialPrefix, -}; -use super::base_conv; -use super::base_conv::RadixDef; - -// if the memory, copy, and comparison cost of chars -// becomes an issue, we can always operate in vec here -// rather than just at de_hex - -pub struct FloatAnalysis { - pub len_important: usize, - // none means no decimal point. - pub decimal_pos: Option, - pub follow: Option, -} -fn has_enough_digits( - hex_input: bool, - hex_output: bool, - string_position: usize, - starting_position: usize, - limit: usize, -) -> bool { - // -1s are for rounding - if hex_output { - if hex_input { - (string_position - 1) - starting_position >= limit - } else { - false //undecidable without converting - } - } else if hex_input { - (((string_position - 1) - starting_position) * 9) / 8 >= limit - } else { - (string_position - 1) - starting_position >= limit - } -} - -impl FloatAnalysis { - #[allow(clippy::cognitive_complexity)] - pub fn analyze( - str_in: &str, - initial_prefix: &InitialPrefix, - max_sd_opt: Option, - max_after_dec_opt: Option, - hex_output: bool, - ) -> Self { - // this fn assumes - // the input string - // has no leading spaces or 0s - let str_it = get_it_at(initial_prefix.offset, str_in); - let mut ret = Self { - len_important: 0, - decimal_pos: None, - follow: None, - }; - let hex_input = match initial_prefix.radix_in { - Base::Hex => true, - Base::Ten => false, - Base::Octal => { - panic!("this should never happen: floats should never receive octal input"); - } - }; - let mut i = 0; - let mut pos_before_first_nonzero_after_decimal: Option = None; - for c in str_it { - match c { - e @ ('0'..='9' | 'A'..='F' | 'a'..='f') => { - if !hex_input { - match e { - '0'..='9' => {} - _ => { - warn_incomplete_conv(str_in); - break; - } - } - } - if ret.decimal_pos.is_some() - && pos_before_first_nonzero_after_decimal.is_none() - && e != '0' - { - pos_before_first_nonzero_after_decimal = Some(i - 1); - } - if let Some(max_sd) = max_sd_opt { - if i == max_sd { - // follow is used in cases of %g - // where the character right after the last - // sd is considered is rounded affecting - // the previous digit in 1/2 of instances - ret.follow = Some(e); - } else if ret.decimal_pos.is_some() && i > max_sd { - break; - } - } - if let Some(max_after_dec) = max_after_dec_opt { - if let Some(p) = ret.decimal_pos { - if has_enough_digits(hex_input, hex_output, i, p, max_after_dec) { - break; - } - } - } else if let Some(max_sd) = max_sd_opt { - if let Some(p) = pos_before_first_nonzero_after_decimal { - if has_enough_digits(hex_input, hex_output, i, p, max_sd) { - break; - } - } - } - } - '.' => { - if ret.decimal_pos.is_none() { - ret.decimal_pos = Some(i); - } else { - warn_incomplete_conv(str_in); - break; - } - } - _ => { - warn_incomplete_conv(str_in); - break; - } - }; - i += 1; - } - ret.len_important = i; - ret - } -} - -fn de_hex(src: &str, before_decimal: bool) -> String { - let radix_ten = base_conv::RadixTen; - let radix_hex = base_conv::RadixHex; - if before_decimal { - base_conv::base_conv_str(src, &radix_hex, &radix_ten) - } else { - let as_arrnum_hex = base_conv::str_to_arrnum(src, &radix_hex); - let s = format!( - "{}", - base_conv::base_conv_float(&as_arrnum_hex, radix_hex.get_max(), radix_ten.get_max()) - ); - if s.len() > 2 { - String::from(&s[2..]) - } else { - // zero - s - } - } -} - -// takes a string in, -// truncates to a position, -// bumps the last digit up one, -// and if the digit was nine -// propagate to the next, etc. -// If before the decimal and the most -// significant digit is a 9, it becomes a 1 -fn _round_str_from(in_str: &str, position: usize, before_dec: bool) -> (String, bool) { - let mut it = in_str[0..position].chars(); - let mut rev = String::new(); - let mut i = position; - let mut finished_in_dec = false; - while let Some(c) = it.next_back() { - i -= 1; - match c { - '9' => { - // If we're before the decimal - // and on the most significant digit, - // round 9 to 1, else to 0. - if before_dec && i == 0 { - rev.push('1'); - } else { - rev.push('0'); - } - } - e => { - rev.push(((e as u8) + 1) as char); - finished_in_dec = true; - break; - } - } - } - let mut fwd = String::from(&in_str[0..i]); - for ch in rev.chars().rev() { - fwd.push(ch); - } - (fwd, finished_in_dec) -} - -fn round_terminal_digit( - before_dec: String, - after_dec: String, - position: usize, -) -> (String, String, bool) { - if position < after_dec.len() { - let digit_at_pos: char; - { - digit_at_pos = after_dec[position..=position].chars().next().expect(""); - } - if let '5'..='9' = digit_at_pos { - let (new_after_dec, finished_in_dec) = _round_str_from(&after_dec, position, false); - if finished_in_dec { - return (before_dec, new_after_dec, false); - } else { - let (new_before_dec, _) = _round_str_from(&before_dec, before_dec.len(), true); - let mut dec_place_chg = false; - let mut before_dec_chars = new_before_dec.chars(); - if before_dec_chars.next() == Some('1') && before_dec_chars.all(|c| c == '0') { - // If the first digit is a one and remaining are zeros, we have - // rounded to a new decimal place, so the decimal place must be updated. - // Only update decimal place if the before decimal != 0 - dec_place_chg = before_dec != "0"; - } - return (new_before_dec, new_after_dec, dec_place_chg); - } - // TODO - } - } - (before_dec, after_dec, false) -} - -#[allow(clippy::cognitive_complexity)] -pub fn get_primitive_dec( - initial_prefix: &InitialPrefix, - str_in: &str, - analysis: &FloatAnalysis, - last_dec_place: usize, - sci_mode: Option, -) -> FormatPrimitive { - let mut f = FormatPrimitive::default(); - - // add negative sign section - if initial_prefix.sign == -1 { - f.prefix = Some(String::from("-")); - } - - // assign the digits before and after the decimal points - // to separate slices. If no digits after decimal point, - // assign 0 - let (mut first_segment_raw, second_segment_raw) = match analysis.decimal_pos { - Some(pos) => (&str_in[..pos], &str_in[pos + 1..]), - None => (str_in, "0"), - }; - if first_segment_raw.is_empty() { - first_segment_raw = "0"; - } - // convert to string, de_hexifying if input is in hex // spell-checker:disable-line - let (first_segment, second_segment) = match initial_prefix.radix_in { - Base::Hex => ( - de_hex(first_segment_raw, true), - de_hex(second_segment_raw, false), - ), - _ => ( - String::from(first_segment_raw), - String::from(second_segment_raw), - ), - }; - let (pre_dec_unrounded, post_dec_unrounded, mut mantissa) = if sci_mode.is_some() { - if first_segment.len() > 1 { - let mut post_dec = String::from(&first_segment[1..]); - post_dec.push_str(&second_segment); - ( - String::from(&first_segment[0..1]), - post_dec, - first_segment.len() as isize - 1, - ) - } else { - match first_segment - .chars() - .next() - .expect("float_common: no chars in first segment.") - { - '0' => { - let it = second_segment.chars().enumerate(); - let mut m: isize = 0; - let mut pre = String::from("0"); - let mut post = String::from("0"); - for (i, c) in it { - match c { - '0' => {} - _ => { - m = -((i as isize) + 1); - pre = String::from(&second_segment[i..=i]); - post = String::from(&second_segment[i + 1..]); - break; - } - } - } - (pre, post, m) - } - _ => (first_segment, second_segment, 0), - } - } - } else { - (first_segment, second_segment, 0) - }; - - let (pre_dec_draft, post_dec_draft, dec_place_chg) = - round_terminal_digit(pre_dec_unrounded, post_dec_unrounded, last_dec_place - 1); - f.post_decimal = Some(post_dec_draft); - if let Some(capitalized) = sci_mode { - let si_ind = if capitalized { 'E' } else { 'e' }; - // Increase the mantissa if we're adding a decimal place - if dec_place_chg { - mantissa += 1; - } - f.suffix = Some(if mantissa >= 0 { - format!("{si_ind}+{mantissa:02}") - } else { - // negative sign is considered in format!s - // leading zeroes - format!("{si_ind}{mantissa:03}") - }); - f.pre_decimal = Some(pre_dec_draft); - } else if dec_place_chg { - // We've rounded up to a new decimal place so append 0 - f.pre_decimal = Some(pre_dec_draft + "0"); - } else { - f.pre_decimal = Some(pre_dec_draft); - } - - f -} - -pub fn primitive_to_str_common(prim: &FormatPrimitive, field: &FormatField) -> String { - let mut final_str = String::new(); - if let Some(ref prefix) = prim.prefix { - final_str.push_str(prefix); - } - match prim.pre_decimal { - Some(ref pre_decimal) => { - final_str.push_str(pre_decimal); - } - None => { - panic!( - "error, format primitives provided to int, will, incidentally under correct \ - behavior, always have a pre_dec value." - ); - } - } - let decimal_places = field.second_field.unwrap_or(6); - match prim.post_decimal { - Some(ref post_decimal) => { - if !post_decimal.is_empty() && decimal_places > 0 { - final_str.push('.'); - let len_avail = post_decimal.len() as u32; - - if decimal_places >= len_avail { - // println!("dec {}, len avail {}", decimal_places, len_avail); - final_str.push_str(post_decimal); - - if *field.field_char != 'g' && *field.field_char != 'G' { - let diff = decimal_places - len_avail; - for _ in 0..diff { - final_str.push('0'); - } - } - } else { - // println!("printing to only {}", decimal_places); - final_str.push_str(&post_decimal[0..decimal_places as usize]); - } - } - } - None => { - panic!( - "error, format primitives provided to int, will, incidentally under correct \ - behavior, always have a pre_dec value." - ); - } - } - if let Some(ref suffix) = prim.suffix { - final_str.push_str(suffix); - } - - final_str -} diff --git a/src/uucore/src/lib/features/tokenize/num_format/formatters/floatf.rs b/src/uucore/src/lib/features/tokenize/num_format/formatters/floatf.rs deleted file mode 100644 index cca2750dc55..00000000000 --- a/src/uucore/src/lib/features/tokenize/num_format/formatters/floatf.rs +++ /dev/null @@ -1,43 +0,0 @@ -// spell-checker:ignore (vars) charf decf floatf intf scif strf Cninety -// spell-checker:ignore (ToDO) arrnum - -//! formatter for %f %F common-notation floating-point subs -use super::super::format_field::FormatField; -use super::super::formatter::{FormatPrimitive, Formatter, InitialPrefix}; -use super::float_common::{get_primitive_dec, primitive_to_str_common, FloatAnalysis}; - -#[derive(Default)] -pub struct Floatf; -impl Floatf { - pub fn new() -> Self { - Self - } -} -impl Formatter for Floatf { - fn get_primitive( - &self, - field: &FormatField, - initial_prefix: &InitialPrefix, - str_in: &str, - ) -> Option { - let second_field = field.second_field.unwrap_or(6) + 1; - let analysis = FloatAnalysis::analyze( - str_in, - initial_prefix, - None, - Some(second_field as usize), - false, - ); - let f = get_primitive_dec( - initial_prefix, - &str_in[initial_prefix.offset..], - &analysis, - second_field as usize, - None, - ); - Some(f) - } - fn primitive_to_str(&self, prim: &FormatPrimitive, field: FormatField) -> String { - primitive_to_str_common(prim, &field) - } -} diff --git a/src/uucore/src/lib/features/tokenize/num_format/formatters/intf.rs b/src/uucore/src/lib/features/tokenize/num_format/formatters/intf.rs deleted file mode 100644 index 0f6e78de6f6..00000000000 --- a/src/uucore/src/lib/features/tokenize/num_format/formatters/intf.rs +++ /dev/null @@ -1,282 +0,0 @@ -// spell-checker:ignore (vars) charf decf floatf intf scif strf Cninety -// spell-checker:ignore (ToDO) arrnum - -//! formatter for unsigned and signed int subs -//! unsigned int: %X %x (hex u64) %o (octal u64) %u (base ten u64) -//! signed int: %i %d (both base ten i64) -use super::super::format_field::FormatField; -use super::super::formatter::{ - get_it_at, warn_incomplete_conv, Base, FormatPrimitive, Formatter, InitialPrefix, -}; -use std::i64; -use std::u64; - -#[derive(Default)] -pub struct Intf { - _a: u32, -} - -// see the Intf::analyze() function below -struct IntAnalysis { - check_past_max: bool, - past_max: bool, - is_zero: bool, - len_digits: u8, -} - -impl Intf { - pub fn new() -> Self { - Self::default() - } - // take a ref to argument string, and basic information - // about prefix (offset, radix, sign), and analyze string - // to gain the IntAnalysis information above - // check_past_max: true if the number *may* be above max, - // but we don't know either way. One of several reasons - // we may have to parse as int. - // past_max: true if the object is past max, false if not - // in the future we should probably combine these into an - // Option - // is_zero: true if number is zero, false otherwise - // len_digits: length of digits used to create the int - // important, for example, if we run into a non-valid character - #[allow(clippy::cognitive_complexity)] - fn analyze(str_in: &str, signed_out: bool, initial_prefix: &InitialPrefix) -> IntAnalysis { - // the maximum number of digits we could conceivably - // have before the decimal point without exceeding the - // max - let mut str_it = get_it_at(initial_prefix.offset, str_in); - let max_sd_in = if signed_out { - match initial_prefix.radix_in { - Base::Ten => 19, - Base::Octal => 21, - Base::Hex => 16, - } - } else { - match initial_prefix.radix_in { - Base::Ten => 20, - Base::Octal => 22, - Base::Hex => 16, - } - }; - let mut ret = IntAnalysis { - check_past_max: false, - past_max: false, - is_zero: false, - len_digits: 0, - }; - - // todo turn this to a while let now that we know - // no special behavior on EOI break - loop { - let c_opt = str_it.next(); - if let Some(c) = c_opt { - match c { - '0'..='9' | 'a'..='f' | 'A'..='F' => { - if ret.len_digits == 0 && c == '0' { - ret.is_zero = true; - } else if ret.is_zero { - ret.is_zero = false; - } - ret.len_digits += 1; - if ret.len_digits == max_sd_in { - if let Some(next_ch) = str_it.next() { - match next_ch { - '0'..='9' => { - ret.past_max = true; - } - _ => { - // force conversion - // to check if its above max. - // todo: spin out convert - // into fn, call it here to try - // read val, on Ok() - // save val for reuse later - // that way on same-base in and out - // we don't needlessly convert int - // to str, we can just copy it over. - ret.check_past_max = true; - str_it.put_back(next_ch); - } - } - if ret.past_max { - break; - } - } else { - ret.check_past_max = true; - } - } - } - _ => { - warn_incomplete_conv(str_in); - break; - } - } - } else { - // breaks on EOL - break; - } - } - ret - } - // get a FormatPrimitive of the maximum value for the field char - // and given sign - fn get_max(field_char: char, sign: i8) -> FormatPrimitive { - let mut fmt_primitive = FormatPrimitive::default(); - fmt_primitive.pre_decimal = Some(String::from(match field_char { - 'd' | 'i' => match sign { - 1 => "9223372036854775807", - _ => { - fmt_primitive.prefix = Some(String::from("-")); - "9223372036854775808" - } - }, - 'x' | 'X' => "ffffffffffffffff", - 'o' => "1777777777777777777777", - /* 'u' | */ _ => "18446744073709551615", - })); - fmt_primitive - } - // conv_from_segment contract: - // 1. takes - // - a string that begins with a non-zero digit, and proceeds - // with zero or more following digits until the end of the string - // - a radix to interpret those digits as - // - a char that communicates: - // whether to interpret+output the string as an i64 or u64 - // what radix to write the parsed number as. - // 2. parses it as a rust integral type - // 3. outputs FormatPrimitive with: - // - if the string falls within bounds: - // number parsed and written in the correct radix - // - if the string falls outside bounds: - // for i64 output, the int minimum or int max (depending on sign) - // for u64 output, the u64 max in the output radix - fn conv_from_segment( - segment: &str, - radix_in: Base, - field_char: char, - sign: i8, - ) -> FormatPrimitive { - match field_char { - 'i' | 'd' => match i64::from_str_radix(segment, radix_in as u32) { - Ok(i) => { - let mut fmt_prim = FormatPrimitive::default(); - if sign == -1 { - fmt_prim.prefix = Some(String::from("-")); - } - fmt_prim.pre_decimal = Some(format!("{i}")); - fmt_prim - } - Err(_) => Self::get_max(field_char, sign), - }, - _ => match u64::from_str_radix(segment, radix_in as u32) { - Ok(u) => { - let mut fmt_prim = FormatPrimitive::default(); - let u_f = if sign == -1 { u64::MAX - (u - 1) } else { u }; - fmt_prim.pre_decimal = Some(match field_char { - 'X' => format!("{u_f:X}"), - 'x' => format!("{u_f:x}"), - 'o' => format!("{u_f:o}"), - _ => format!("{u_f}"), - }); - fmt_prim - } - Err(_) => Self::get_max(field_char, sign), - }, - } - } -} -impl Formatter for Intf { - fn get_primitive( - &self, - field: &FormatField, - initial_prefix: &InitialPrefix, - str_in: &str, - ) -> Option { - let begin = initial_prefix.offset; - - // get information about the string. see Intf::Analyze - // def above. - let convert_hints = Self::analyze( - str_in, - *field.field_char == 'i' || *field.field_char == 'd', - initial_prefix, - ); - // We always will have a format primitive to return - Some(if convert_hints.len_digits == 0 || convert_hints.is_zero { - // if non-digit or end is reached before a non-zero digit - FormatPrimitive { - pre_decimal: Some(String::from("0")), - ..Default::default() - } - } else if !convert_hints.past_max { - // if the number is or may be below the bounds limit - let radix_out = match *field.field_char { - 'd' | 'i' | 'u' => Base::Ten, - 'x' | 'X' => Base::Hex, - /* 'o' | */ _ => Base::Octal, - }; - let radix_mismatch = !radix_out.eq(&initial_prefix.radix_in); - let decrease_from_max: bool = initial_prefix.sign == -1 && *field.field_char != 'i'; - let end = begin + convert_hints.len_digits as usize; - - // convert to int if any one of these is true: - // - number of digits in int indicates it may be past max - // - we're subtracting from the max - // - we're converting the base - if convert_hints.check_past_max || decrease_from_max || radix_mismatch { - // radix of in and out is the same. - let segment = String::from(&str_in[begin..end]); - Self::conv_from_segment( - &segment, - initial_prefix.radix_in.clone(), - *field.field_char, - initial_prefix.sign, - ) - } else { - // otherwise just do a straight string copy. - let mut fmt_prim = FormatPrimitive::default(); - - // this is here and not earlier because - // zero doesn't get a sign, and conv_from_segment - // creates its format primitive separately - if initial_prefix.sign == -1 && *field.field_char == 'i' { - fmt_prim.prefix = Some(String::from("-")); - } - fmt_prim.pre_decimal = Some(String::from(&str_in[begin..end])); - fmt_prim - } - } else { - Self::get_max(*field.field_char, initial_prefix.sign) - }) - } - fn primitive_to_str(&self, prim: &FormatPrimitive, field: FormatField) -> String { - let mut final_str: String = String::new(); - if let Some(ref prefix) = prim.prefix { - final_str.push_str(prefix); - } - // integral second fields is zero-padded minimum-width - // which gets handled before general minimum-width - match prim.pre_decimal { - Some(ref pre_decimal) => { - if let Some(min) = field.second_field { - let mut i = min; - let len = pre_decimal.len() as u32; - while i > len { - final_str.push('0'); - i -= 1; - } - } - final_str.push_str(pre_decimal); - } - None => { - panic!( - "error, format primitives provided to int, will, incidentally under \ - correct behavior, always have a pre_dec value." - ); - } - } - final_str - } -} diff --git a/src/uucore/src/lib/features/tokenize/num_format/formatters/mod.rs b/src/uucore/src/lib/features/tokenize/num_format/formatters/mod.rs deleted file mode 100644 index e232300718b..00000000000 --- a/src/uucore/src/lib/features/tokenize/num_format/formatters/mod.rs +++ /dev/null @@ -1,9 +0,0 @@ -// spell-checker:ignore (vars) charf cninetyninehexfloatf decf floatf intf scif strf Cninety - -mod base_conv; -pub mod cninetyninehexfloatf; -pub mod decf; -mod float_common; -pub mod floatf; -pub mod intf; -pub mod scif; diff --git a/src/uucore/src/lib/features/tokenize/num_format/formatters/scif.rs b/src/uucore/src/lib/features/tokenize/num_format/formatters/scif.rs deleted file mode 100644 index c871dc4e552..00000000000 --- a/src/uucore/src/lib/features/tokenize/num_format/formatters/scif.rs +++ /dev/null @@ -1,43 +0,0 @@ -// spell-checker:ignore (vars) charf cninetyninehexfloatf decf floatf intf scif strf Cninety - -//! formatter for %e %E scientific notation subs -use super::super::format_field::FormatField; -use super::super::formatter::{FormatPrimitive, Formatter, InitialPrefix}; -use super::float_common::{get_primitive_dec, primitive_to_str_common, FloatAnalysis}; - -#[derive(Default)] -pub struct Scif; - -impl Scif { - pub fn new() -> Self { - Self - } -} -impl Formatter for Scif { - fn get_primitive( - &self, - field: &FormatField, - initial_prefix: &InitialPrefix, - str_in: &str, - ) -> Option { - let second_field = field.second_field.unwrap_or(6) + 1; - let analysis = FloatAnalysis::analyze( - str_in, - initial_prefix, - Some(second_field as usize + 1), - None, - false, - ); - let f = get_primitive_dec( - initial_prefix, - &str_in[initial_prefix.offset..], - &analysis, - second_field as usize, - Some(*field.field_char == 'E'), - ); - Some(f) - } - fn primitive_to_str(&self, prim: &FormatPrimitive, field: FormatField) -> String { - primitive_to_str_common(prim, &field) - } -} diff --git a/src/uucore/src/lib/features/tokenize/num_format/mod.rs b/src/uucore/src/lib/features/tokenize/num_format/mod.rs deleted file mode 100644 index d40cf92deff..00000000000 --- a/src/uucore/src/lib/features/tokenize/num_format/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub mod format_field; -mod formatter; -mod formatters; -pub mod num_format; diff --git a/src/uucore/src/lib/features/tokenize/num_format/num_format.rs b/src/uucore/src/lib/features/tokenize/num_format/num_format.rs deleted file mode 100644 index c9b1178b6ac..00000000000 --- a/src/uucore/src/lib/features/tokenize/num_format/num_format.rs +++ /dev/null @@ -1,271 +0,0 @@ -// spell-checker:ignore (vars) charf cninetyninehexfloatf decf floatf intf scif strf Cninety - -//! handles creating printed output for numeric substitutions - -// spell-checker:ignore (vars) charf decf floatf intf scif strf Cninety - -use std::env; -use std::vec::Vec; - -use crate::display::Quotable; -use crate::{show_error, show_warning}; - -use super::format_field::{FieldType, FormatField}; -use super::formatter::{Base, FormatPrimitive, Formatter, InitialPrefix}; -use super::formatters::cninetyninehexfloatf::CninetyNineHexFloatf; -use super::formatters::decf::Decf; -use super::formatters::floatf::Floatf; -use super::formatters::intf::Intf; -use super::formatters::scif::Scif; - -pub fn warn_expected_numeric(pf_arg: &str) { - // important: keep println here not print - show_error!("{}: expected a numeric value", pf_arg.maybe_quote()); -} - -// when character constant arguments have excess characters -// issue a warning when POSIXLY_CORRECT is not set -fn warn_char_constant_ign(remaining_bytes: &[u8]) { - match env::var("POSIXLY_CORRECT") { - Ok(_) => {} - Err(e) => { - if let env::VarError::NotPresent = e { - show_warning!( - "{:?}: character(s) following character \ - constant have been ignored", - remaining_bytes - ); - } - } - } -} - -// this function looks at the first few -// characters of an argument and returns a value if we can learn -// a value from that (e.g. no argument? return 0, char constant? ret value) -fn get_provided(str_in_opt: Option<&String>) -> Option { - const C_S_QUOTE: u8 = 39; - const C_D_QUOTE: u8 = 34; - match str_in_opt { - Some(str_in) => { - let mut byte_it = str_in.bytes(); - if let Some(ch) = byte_it.next() { - match ch { - C_S_QUOTE | C_D_QUOTE => { - Some(match byte_it.next() { - Some(second_byte) => { - let mut ignored: Vec = Vec::new(); - for cont in byte_it { - ignored.push(cont); - } - if !ignored.is_empty() { - warn_char_constant_ign(&ignored); - } - second_byte - } - // no byte after quote - None => { - let so_far = (ch as char).to_string(); - warn_expected_numeric(&so_far); - 0_u8 - } - }) - } - // first byte is not quote - _ => None, // no first byte - } - } else { - Some(0_u8) - } - } - None => Some(0), - } -} - -// takes a string and returns -// a sign, -// a base, -// and an offset for index after all -// initial spacing, sign, base prefix, and leading zeroes -#[allow(clippy::cognitive_complexity)] -fn get_initial_prefix(str_in: &str, field_type: &FieldType) -> InitialPrefix { - let mut str_it = str_in.chars(); - let mut ret = InitialPrefix { - radix_in: Base::Ten, - sign: 1, - offset: 0, - }; - let mut top_char = str_it.next(); - // skip spaces and ensure top_char is the first non-space char - // (or None if none exists) - while let Some(' ') = top_char { - ret.offset += 1; - top_char = str_it.next(); - } - // parse sign - match top_char { - Some('+') => { - ret.offset += 1; - top_char = str_it.next(); - } - Some('-') => { - ret.sign = -1; - ret.offset += 1; - top_char = str_it.next(); - } - _ => {} - } - // we want to exit with offset being - // the index of the first non-zero - // digit before the decimal point or - // if there is none, the zero before the - // decimal point, or, if there is none, - // the decimal point. - - // while we are determining the offset - // we will ensure as a convention - // the offset is always on the first character - // that we are yet unsure if it is the - // final offset. If the zero could be before - // a decimal point we don't move past the zero. - let mut is_hex = false; - if Some('0') == top_char { - if let Some(base) = str_it.next() { - // lead zeroes can only exist in - // octal and hex base - let mut do_clean_lead_zeroes = false; - match base { - 'x' | 'X' => { - is_hex = true; - ret.offset += 2; - ret.radix_in = Base::Hex; - do_clean_lead_zeroes = true; - } - e @ '0'..='9' => { - ret.offset += 1; - if let FieldType::Intf = *field_type { - ret.radix_in = Base::Octal; - } - if e == '0' { - do_clean_lead_zeroes = true; - } - } - _ => {} - } - if do_clean_lead_zeroes { - let mut first = true; - for ch_zero in str_it { - // see notes on offset above: - // this is why the offset for octal and decimal numbers - // that reach this branch is 1 even though - // they have already eaten the characters '00' - // this is also why when hex encounters its - // first zero it does not move its offset - // forward because it does not know for sure - // that it's current offset (of that zero) - // is not the final offset, - // whereas at that point octal knows its - // current offset is not the final offset. - match ch_zero { - '0' => { - if !(is_hex && first) { - ret.offset += 1; - } - } - // if decimal, keep last zero if one exists - // (it's possible for last zero to - // not exist at this branch if we're in hex input) - '.' => break, - // other digit, etc. - _ => { - if !(is_hex && first) { - ret.offset += 1; - } - break; - } - } - if first { - first = false; - } - } - } - } - } - ret -} - -// this is the function a Sub's print will delegate to -// if it is a numeric field, passing the field details -// and an iterator to the argument -pub fn num_format(field: &FormatField, in_str_opt: Option<&String>) -> Option { - let field_char = field.field_char; - - // num format mainly operates by further delegating to one of - // several Formatter structs depending on the field - // see formatter.rs for more details - - // to do switch to static dispatch - let formatter: Box = match *field.field_type { - FieldType::Intf => Box::new(Intf::new()), - FieldType::Floatf => Box::new(Floatf::new()), - FieldType::CninetyNineHexFloatf => Box::new(CninetyNineHexFloatf::new()), - FieldType::Scif => Box::new(Scif::new()), - FieldType::Decf => Box::new(Decf::new()), - _ => { - panic!("asked to do num format with non-num field type"); - } - }; - let prim_opt= - // if we can get an assumed value from looking at the first - // few characters, use that value to create the FormatPrimitive - if let Some(provided_num) = get_provided(in_str_opt) { - let mut tmp = FormatPrimitive::default(); - match field_char { - 'u' | 'i' | 'd' => { - tmp.pre_decimal = Some( - format!("{provided_num}")); - }, - 'x' | 'X' => { - tmp.pre_decimal = Some( - format!("{provided_num:x}")); - }, - 'o' => { - tmp.pre_decimal = Some( - format!("{provided_num:o}")); - }, - 'e' | 'E' | 'g' | 'G' => { - let as_str = format!("{provided_num}"); - let initial_prefix = get_initial_prefix( - &as_str, - field.field_type - ); - tmp=formatter.get_primitive(field, &initial_prefix, &as_str) - .expect("err during default provided num"); - }, - _ => { - tmp.pre_decimal = Some( - format!("{provided_num}")); - tmp.post_decimal = Some(String::from("0")); - } - } - Some(tmp) - } else { - // otherwise we'll interpret the argument as a number - // using the appropriate Formatter - let in_str = in_str_opt.expect( - "please send the devs this message: - \n get_provided is failing to ret as Some(0) on no str "); - // first get information about the beginning of the - // numeric argument that would be useful for - // any formatter (int or float) - let initial_prefix = get_initial_prefix( - in_str, - field.field_type - ); - // then get the FormatPrimitive from the Formatter - formatter.get_primitive(field, &initial_prefix, in_str) - }; - // if we have a formatPrimitive, print its results - // according to the field-char appropriate Formatter - prim_opt.map(|prim| formatter.primitive_to_str(&prim, field.clone())) -} diff --git a/src/uucore/src/lib/features/tokenize/sub.rs b/src/uucore/src/lib/features/tokenize/sub.rs deleted file mode 100644 index 5bdb24dc633..00000000000 --- a/src/uucore/src/lib/features/tokenize/sub.rs +++ /dev/null @@ -1,452 +0,0 @@ -// spell-checker:ignore (vars) charf decf floatf intf scif strf Cninety - -//! Sub is a token that represents a -//! segment of the format string that is a substitution -//! it is created by Sub's implementation of the Tokenizer trait -//! Subs which have numeric field chars make use of the num_format -//! submodule -use crate::error::{UError, UResult}; -use itertools::{put_back_n, PutBackN}; -use std::error::Error; -use std::fmt::Display; -use std::io::Write; -use std::iter::Peekable; -use std::process::exit; -use std::slice::Iter; -use std::str::Chars; -// use std::collections::HashSet; - -use super::num_format::format_field::{FieldType, FormatField}; -use super::num_format::num_format; -use super::token; -use super::unescaped_text::UnescapedText; - -const EXIT_ERR: i32 = 1; - -#[derive(Debug)] -pub enum SubError { - InvalidSpec(String), -} - -impl Display for SubError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { - match self { - Self::InvalidSpec(s) => write!(f, "%{s}: invalid conversion specification"), - } - } -} - -impl Error for SubError {} - -impl UError for SubError {} - -fn convert_asterisk_arg_int(asterisk_arg: &str) -> isize { - // this is a costly way to parse the - // args used for asterisk values into integers - // from various bases. Actually doing it correctly - // (going through the pipeline to intf, but returning - // the integer instead of writing it to string and then - // back) is on the refactoring TODO - let field_type = FieldType::Intf; - let field_char = 'i'; - let field_info = FormatField { - min_width: Some(0), - second_field: Some(0), - orig: &asterisk_arg.to_string(), - field_type: &field_type, - field_char: &field_char, - }; - num_format::num_format(&field_info, Some(&asterisk_arg.to_string())) - .unwrap() - .parse::() - .unwrap() -} - -pub enum CanAsterisk { - Fixed(T), - Asterisk, -} - -// Sub is a tokenizer which creates tokens -// for substitution segments of a format string -pub struct Sub { - min_width: CanAsterisk>, - second_field: CanAsterisk>, - field_char: char, - field_type: FieldType, - orig: String, - prefix_char: char, -} -impl Sub { - pub fn new( - min_width: CanAsterisk>, - second_field: CanAsterisk>, - field_char: char, - orig: String, - prefix_char: char, - ) -> Self { - // for more dry printing, field characters are grouped - // in initialization of token. - let field_type = match field_char { - 's' | 'b' => FieldType::Strf, - 'd' | 'i' | 'u' | 'o' | 'x' | 'X' => FieldType::Intf, - 'f' | 'F' => FieldType::Floatf, - 'a' | 'A' => FieldType::CninetyNineHexFloatf, - 'e' | 'E' => FieldType::Scif, - 'g' | 'G' => FieldType::Decf, - 'c' => FieldType::Charf, - _ => { - // should be unreachable. - println!("Invalid field type"); - exit(EXIT_ERR); - } - }; - Self { - min_width, - second_field, - field_char, - field_type, - orig, - prefix_char, - } - } -} - -#[derive(Default)] -pub(crate) struct SubParser { - min_width_tmp: Option, - min_width_is_asterisk: bool, - past_decimal: bool, - second_field_tmp: Option, - second_field_is_asterisk: bool, - specifiers_found: bool, - field_char: Option, - text_so_far: String, -} - -impl SubParser { - fn new() -> Self { - Self::default() - } - pub(crate) fn from_it( - writer: &mut W, - it: &mut PutBackN, - args: &mut Peekable>, - ) -> UResult> - where - W: Write, - { - let mut parser = Self::new(); - if parser.sub_vals_retrieved(it)? { - let t = Self::build_token(parser); - t.write(writer, args); - Ok(Some(t)) - } else { - Ok(None) - } - } - fn build_token(parser: Self) -> token::Token { - // not a self method so as to allow move of sub-parser vals. - // return new Sub struct as token - let prefix_char = match &parser.min_width_tmp { - Some(width) if width.starts_with('0') => '0', - _ => ' ', - }; - - token::Token::Sub(Sub::new( - if parser.min_width_is_asterisk { - CanAsterisk::Asterisk - } else { - CanAsterisk::Fixed( - parser - .min_width_tmp - .map(|x| x.parse::().unwrap_or(1)), - ) - }, - if parser.second_field_is_asterisk { - CanAsterisk::Asterisk - } else { - CanAsterisk::Fixed(parser.second_field_tmp.map(|x| x.parse::().unwrap())) - }, - parser.field_char.unwrap(), - parser.text_so_far, - prefix_char, - )) - } - #[allow(clippy::cognitive_complexity)] - fn sub_vals_retrieved(&mut self, it: &mut PutBackN) -> UResult { - if !Self::successfully_eat_prefix(it, &mut self.text_so_far)? { - return Ok(false); - } - // this fn in particular is much longer than it needs to be - // .could get a lot - // of code savings just by cleaning it up. shouldn't use a regex - // though, as we want to mimic the original behavior of printing - // the field as interpreted up until the error in the field. - - let mut legal_fields = vec![ - // 'a', 'A', //c99 hex float implementation not yet complete - 'b', 'c', 'd', 'e', 'E', 'f', 'F', 'g', 'G', 'i', 'o', 's', 'u', 'x', 'X', - ]; - let mut specifiers = vec!['h', 'j', 'l', 'L', 't', 'z']; - legal_fields.sort_unstable(); - specifiers.sort_unstable(); - - // divide substitution from %([0-9]+)?(.[0-9+])?([a-zA-Z]) - // into min_width, second_field, field_char - for ch in it { - self.text_so_far.push(ch); - match ch { - '-' | '*' | '0'..='9' => { - if self.past_decimal { - // second field should never have a - // negative value - if self.second_field_is_asterisk || ch == '-' || self.specifiers_found { - return Err(SubError::InvalidSpec(self.text_so_far.clone()).into()); - } - if self.second_field_tmp.is_none() { - self.second_field_tmp = Some(String::new()); - } - match self.second_field_tmp.as_mut() { - Some(x) => { - if ch == '*' && !x.is_empty() { - return Err( - SubError::InvalidSpec(self.text_so_far.clone()).into() - ); - } - if ch == '*' { - self.second_field_is_asterisk = true; - } - x.push(ch); - } - None => { - panic!("should be unreachable"); - } - } - } else { - if self.min_width_is_asterisk || self.specifiers_found { - return Err(SubError::InvalidSpec(self.text_so_far.clone()).into()); - } - if self.min_width_tmp.is_none() { - self.min_width_tmp = Some(String::new()); - } - match self.min_width_tmp.as_mut() { - Some(x) => { - if (ch == '-' || ch == '*') && !x.is_empty() { - return Err( - SubError::InvalidSpec(self.text_so_far.clone()).into() - ); - } - if ch == '*' { - self.min_width_is_asterisk = true; - } - x.push(ch); - } - None => { - panic!("should be unreachable"); - } - } - } - } - '.' => { - if self.past_decimal { - return Err(SubError::InvalidSpec(self.text_so_far.clone()).into()); - } else { - self.past_decimal = true; - } - } - x if legal_fields.binary_search(&x).is_ok() => { - self.field_char = Some(ch); - self.text_so_far.push(ch); - break; - } - x if specifiers.binary_search(&x).is_ok() => { - if !self.past_decimal { - self.past_decimal = true; - } - if !self.specifiers_found { - self.specifiers_found = true; - } - } - _ => { - return Err(SubError::InvalidSpec(self.text_so_far.clone()).into()); - } - } - } - if self.field_char.is_none() { - return Err(SubError::InvalidSpec(self.text_so_far.clone()).into()); - } - let field_char_retrieved = self.field_char.unwrap(); - if self.past_decimal && self.second_field_tmp.is_none() { - self.second_field_tmp = Some(String::from("0")); - } - self.validate_field_params(field_char_retrieved)?; - // if the dot is provided without a second field - // printf interprets it as 0. - if let Some(x) = self.second_field_tmp.as_mut() { - if x.is_empty() { - self.min_width_tmp = Some(String::from("0")); - } - } - - Ok(true) - } - fn successfully_eat_prefix( - it: &mut PutBackN, - text_so_far: &mut String, - ) -> UResult { - // get next two chars, - // if they're '%%' we're not tokenizing it - // else put chars back - let preface = it.next(); - let n_ch = it.next(); - if preface == Some('%') && n_ch != Some('%') { - match n_ch { - Some(x) => { - it.put_back(x); - Ok(true) - } - None => { - text_so_far.push('%'); - Err(SubError::InvalidSpec(text_so_far.clone()).into()) - } - } - } else { - if let Some(x) = n_ch { - it.put_back(x); - }; - if let Some(x) = preface { - it.put_back(x); - }; - Ok(false) - } - } - fn validate_field_params(&self, field_char: char) -> UResult<()> { - // check for illegal combinations here when possible vs - // on each application so we check less per application - // to do: move these checks to Sub::new - if (field_char == 's' && self.min_width_tmp == Some(String::from("0"))) - || (field_char == 'c' - && (self.min_width_tmp == Some(String::from("0")) || self.past_decimal)) - || (field_char == 'b' - && (self.min_width_tmp.is_some() - || self.past_decimal - || self.second_field_tmp.is_some())) - { - // invalid string substitution - // to do: include information about an invalid - // string substitution - return Err(SubError::InvalidSpec(self.text_so_far.clone()).into()); - } - Ok(()) - } -} - -impl Sub { - #[allow(clippy::cognitive_complexity)] - pub(crate) fn write(&self, writer: &mut W, pf_args_it: &mut Peekable>) - where - W: Write, - { - let field = FormatField { - min_width: match self.min_width { - CanAsterisk::Fixed(x) => x, - CanAsterisk::Asterisk => { - match pf_args_it.next() { - // temporary, use intf.rs instead - Some(x) => Some(convert_asterisk_arg_int(x)), - None => Some(0), - } - } - }, - second_field: match self.second_field { - CanAsterisk::Fixed(x) => x, - CanAsterisk::Asterisk => { - match pf_args_it.next() { - // temporary, use intf.rs instead - Some(x) => { - let result = convert_asterisk_arg_int(x); - if result < 0 { - None - } else { - Some(result as u32) - } - } - None => Some(0), - } - } - }, - field_char: &self.field_char, - field_type: &self.field_type, - orig: &self.orig, - }; - let pf_arg = pf_args_it.next(); - - // minimum width is handled independently of actual - // field char - let pre_min_width_opt: Option = match *field.field_type { - // if %s just return arg - // if %b use UnescapedText module's unescape-fn - // if %c return first char of arg - FieldType::Strf | FieldType::Charf => { - match pf_arg { - Some(arg_string) => { - match *field.field_char { - 's' => Some(match field.second_field { - Some(max) => String::from(&arg_string[..max as usize]), - None => arg_string.clone(), - }), - 'b' => { - let mut a_it = put_back_n(arg_string.chars()); - UnescapedText::from_it_core(writer, &mut a_it, true); - None - } - // for 'c': get iter of string vals, - // get opt of first val - // and map it to opt - /* 'c' | */ - _ => arg_string.chars().next().map(|x| x.to_string()), - } - } - None => None, - } - } - _ => { - // non string/char fields are delegated to num_format - num_format::num_format(&field, pf_arg) - } - }; - if let Some(pre_min_width) = pre_min_width_opt { - // if have a string, print it, ensuring minimum width is met. - write!( - writer, - "{}", - match field.min_width { - Some(min_width) => { - let diff: isize = min_width.abs() - pre_min_width.len() as isize; - if diff > 0 { - let mut final_str = String::new(); - // definitely more efficient ways - // to do this. - let pad_before = min_width > 0; - if !pad_before { - final_str.push_str(&pre_min_width); - } - for _ in 0..diff { - final_str.push(self.prefix_char); - } - if pad_before { - final_str.push_str(&pre_min_width); - } - final_str - } else { - pre_min_width - } - } - None => pre_min_width, - } - ) - .ok(); - } - } -} diff --git a/src/uucore/src/lib/features/tokenize/token.rs b/src/uucore/src/lib/features/tokenize/token.rs deleted file mode 100644 index b522c99a4e0..00000000000 --- a/src/uucore/src/lib/features/tokenize/token.rs +++ /dev/null @@ -1,39 +0,0 @@ -//! Traits and enums dealing with Tokenization of printf Format String -use std::io::Write; -use std::iter::Peekable; -use std::slice::Iter; - -use crate::features::tokenize::sub::Sub; -use crate::features::tokenize::unescaped_text::UnescapedText; - -// A token object is an object that can print the expected output -// of a contiguous segment of the format string, and -// requires at most 1 argument -pub enum Token { - Sub(Sub), - UnescapedText(UnescapedText), -} - -impl Token { - pub(crate) fn write(&self, writer: &mut W, args: &mut Peekable>) - where - W: Write, - { - match self { - Self::Sub(sub) => sub.write(writer, args), - Self::UnescapedText(unescaped_text) => unescaped_text.write(writer), - } - } -} - -// A tokenizer object is an object that takes an iterator -// at a position in a format string, and sees whether -// it can return a token of a type it knows how to produce -// if so, return the token, move the iterator past the -// format string text the token represents, and if an -// argument is used move the argument iter forward one - -// creating token of a format string segment should also cause -// printing of that token's value. Essentially tokenizing -// a whole format string will print the format string and consume -// a number of arguments equal to the number of argument-using tokens diff --git a/src/uucore/src/lib/features/tokenize/unescaped_text.rs b/src/uucore/src/lib/features/tokenize/unescaped_text.rs deleted file mode 100644 index 29c657ed863..00000000000 --- a/src/uucore/src/lib/features/tokenize/unescaped_text.rs +++ /dev/null @@ -1,279 +0,0 @@ -//! UnescapedText is a tokenizer impl -//! for tokenizing character literals, -//! and escaped character literals (of allowed escapes), -//! into an unescaped text byte array - -// spell-checker:ignore (ToDO) retval hexchars octals printf's bvec vals coreutil addchar eval bytecode bslice - -use itertools::PutBackN; -use std::char::from_u32; -use std::io::Write; -use std::process::exit; -use std::str::Chars; - -use super::token; - -const EXIT_OK: i32 = 0; -const EXIT_ERR: i32 = 1; - -// by default stdout only flushes -// to console when a newline is passed. -macro_rules! write_and_flush { - ($writer:expr, $($args:tt)+) => ({ - write!($writer, "{}", $($args)+).ok(); - $writer.flush().ok(); - }) -} - -fn flush_bytes(writer: &mut W, bslice: &[u8]) -where - W: Write, -{ - writer.write_all(bslice).ok(); - writer.flush().ok(); -} - -#[derive(Default)] -pub struct UnescapedText(Vec); -impl UnescapedText { - fn new() -> Self { - Self::default() - } - // take an iterator to the format string - // consume between min and max chars - // and return it as a base-X number - fn base_to_u32(min_chars: u8, max_chars: u8, base: u32, it: &mut PutBackN) -> u32 { - let mut retval: u32 = 0; - let mut found = 0; - while found < max_chars { - // if end of input break - let nc = it.next(); - match nc { - Some(digit) => { - // if end of hexchars break - match digit.to_digit(base) { - Some(d) => { - found += 1; - retval *= base; - retval += d; - } - None => { - it.put_back(digit); - break; - } - } - } - None => { - break; - } - } - } - if found < min_chars { - // only ever expected for hex - println!("missing hexadecimal number in escape"); //todo stderr - exit(EXIT_ERR); - } - retval - } - // validates against valid - // IEC 10646 vals - these values - // are pinned against the more popular - // printf so as to not disrupt when - // dropped-in as a replacement. - fn validate_iec(val: u32, eight_word: bool) { - let mut preface = 'u'; - let leading_zeros = if eight_word { - preface = 'U'; - 8 - } else { - 4 - }; - let err_msg = format!("invalid universal character name {preface}{val:0leading_zeros$x}"); - if (val < 159 && (val != 36 && val != 64 && val != 96)) || (val > 55296 && val < 57343) { - println!("{err_msg}"); //todo stderr - exit(EXIT_ERR); - } - } - // pass an iterator that succeeds an '/', - // and process the remaining character - // adding the unescaped bytes - // to the passed byte_vec - // in subs_mode change octal behavior - fn handle_escaped( - writer: &mut W, - byte_vec: &mut Vec, - it: &mut PutBackN, - subs_mode: bool, - ) where - W: Write, - { - let ch = it.next().unwrap_or('\\'); - match ch { - '0'..='9' | 'x' => { - let min_len = 1; - let mut max_len = 2; - let mut base = 16; - let ignore = false; - match ch { - 'x' => {} - e @ '0'..='9' => { - max_len = 3; - base = 8; - // in practice, gnu coreutils printf - // interprets octals without a - // leading zero in %b - // but it only skips leading zeros - // in %b mode. - // if we ever want to match gnu coreutil - // printf's docs instead of its behavior - // we'd set this to true. - // if subs_mode && e != '0' - // { ignore = true; } - if !subs_mode || e != '0' { - it.put_back(ch); - } - } - _ => {} - } - if ignore { - byte_vec.push(ch as u8); - } else { - let val = (Self::base_to_u32(min_len, max_len, base, it) % 256) as u8; - byte_vec.push(val); - let bvec = [val]; - flush_bytes(writer, &bvec); - } - } - e => { - // only for hex and octal - // is byte encoding specified. - // otherwise, why not leave the door open - // for other encodings unless it turns out - // a bottleneck. - let mut s = String::new(); - let ch = match e { - '\\' => '\\', - '"' => '"', - 'n' => '\n', - 'r' => '\r', - 't' => '\t', - // bell - 'a' => '\x07', - // backspace - 'b' => '\x08', - // vertical tab - 'v' => '\x0B', - // form feed - 'f' => '\x0C', - // escape character - 'e' => '\x1B', - 'c' => exit(EXIT_OK), - 'u' | 'U' => { - let len = match e { - 'u' => 4, - /* 'U' | */ _ => 8, - }; - let val = Self::base_to_u32(len, len, 16, it); - Self::validate_iec(val, false); - if let Some(c) = from_u32(val) { - c - } else { - '-' - } - } - _ => { - s.push('\\'); - ch - } - }; - s.push(ch); - write_and_flush!(writer, &s); - byte_vec.extend(s.bytes()); - } - }; - } - - // take an iterator to a string, - // and return a wrapper around a Vec of unescaped bytes - // break on encounter of sub symbol ('%[^%]') unless called - // through %b subst. - #[allow(clippy::cognitive_complexity)] - pub fn from_it_core( - writer: &mut W, - it: &mut PutBackN, - subs_mode: bool, - ) -> Option - where - W: Write, - { - let mut addchar = false; - let mut new_text = Self::new(); - let mut tmp_str = String::new(); - { - let new_vec: &mut Vec = &mut (new_text.0); - while let Some(ch) = it.next() { - if !addchar { - addchar = true; - } - match ch { - x if x != '\\' && x != '%' => { - // lazy branch eval - // remember this fn could be called - // many times in a single exec through %b - write_and_flush!(writer, ch); - tmp_str.push(ch); - } - '\\' => { - // the literal may be a literal bytecode - // and not valid utf-8. Str only supports - // valid utf-8. - // if we find the unnecessary drain - // on non hex or octal escapes is costly - // then we can make it faster/more complex - // with as-necessary draining. - if !tmp_str.is_empty() { - new_vec.extend(tmp_str.bytes()); - tmp_str = String::new(); - } - Self::handle_escaped(writer, new_vec, it, subs_mode); - } - x if x == '%' && !subs_mode => { - if let Some(follow) = it.next() { - if follow == '%' { - write_and_flush!(writer, ch); - tmp_str.push(ch); - } else { - it.put_back(follow); - it.put_back(ch); - break; - } - } else { - it.put_back(ch); - break; - } - } - _ => { - write_and_flush!(writer, ch); - tmp_str.push(ch); - } - } - } - if !tmp_str.is_empty() { - new_vec.extend(tmp_str.bytes()); - } - } - if addchar { - Some(token::Token::UnescapedText(new_text)) - } else { - None - } - } -} -impl UnescapedText { - pub(crate) fn write(&self, writer: &mut W) - where - W: Write, - { - flush_bytes(writer, &self.0[..]); - } -} diff --git a/src/uucore/src/lib/lib.rs b/src/uucore/src/lib/lib.rs index ca9a48d258a..7f5cc99db34 100644 --- a/src/uucore/src/lib/lib.rs +++ b/src/uucore/src/lib/lib.rs @@ -44,8 +44,8 @@ pub use crate::features::fs; pub use crate::features::fsext; #[cfg(feature = "lines")] pub use crate::features::lines; -#[cfg(feature = "memo")] -pub use crate::features::memo; +#[cfg(feature = "format")] +pub use crate::features::format; #[cfg(feature = "ringbuffer")] pub use crate::features::ringbuffer; #[cfg(feature = "sum")] From 66eb64e41f1da4c32d692da957847c09eb478e17 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Wed, 2 Aug 2023 23:57:53 +0200 Subject: [PATCH 0005/2851] dd, printf, seq: update to new printf --- src/uu/dd/Cargo.toml | 2 +- src/uu/dd/src/progress.rs | 2 +- src/uu/printf/Cargo.toml | 2 +- src/uu/printf/src/printf.rs | 2 +- src/uu/seq/Cargo.toml | 2 +- src/uu/seq/src/seq.rs | 19 +++++-------------- 6 files changed, 10 insertions(+), 19 deletions(-) diff --git a/src/uu/dd/Cargo.toml b/src/uu/dd/Cargo.toml index 0a69ae37435..aa19dc760e1 100644 --- a/src/uu/dd/Cargo.toml +++ b/src/uu/dd/Cargo.toml @@ -18,7 +18,7 @@ path = "src/dd.rs" clap = { workspace = true } gcd = { workspace = true } libc = { workspace = true } -uucore = { workspace = true, features = ["memo"] } +uucore = { workspace = true, features = ["format"] } [target.'cfg(any(target_os = "linux"))'.dependencies] nix = { workspace = true, features = ["fs"] } diff --git a/src/uu/dd/src/progress.rs b/src/uu/dd/src/progress.rs index a9d29ff6325..674d90984ca 100644 --- a/src/uu/dd/src/progress.rs +++ b/src/uu/dd/src/progress.rs @@ -14,7 +14,7 @@ use std::sync::mpsc; use std::time::Duration; use uucore::error::UResult; -use uucore::memo::sprintf; +use uucore::format::sprintf; use crate::numbers::{to_magnitude_and_suffix, SuffixType}; diff --git a/src/uu/printf/Cargo.toml b/src/uu/printf/Cargo.toml index eefcf33c07e..9acd2c78c9d 100644 --- a/src/uu/printf/Cargo.toml +++ b/src/uu/printf/Cargo.toml @@ -16,7 +16,7 @@ path = "src/printf.rs" [dependencies] clap = { workspace = true } -uucore = { workspace = true, features = ["memo"] } +uucore = { workspace = true, features = ["format"] } [[bin]] name = "printf" diff --git a/src/uu/printf/src/printf.rs b/src/uu/printf/src/printf.rs index bf79369ccab..042e0932eaf 100644 --- a/src/uu/printf/src/printf.rs +++ b/src/uu/printf/src/printf.rs @@ -4,7 +4,7 @@ use clap::{crate_version, Arg, ArgAction, Command}; use uucore::error::{UResult, UUsageError}; -use uucore::memo::printf; +use uucore::format::printf; use uucore::{format_usage, help_about, help_section, help_usage}; const VERSION: &str = "version"; diff --git a/src/uu/seq/Cargo.toml b/src/uu/seq/Cargo.toml index 2646d36095d..41b42ef83a4 100644 --- a/src/uu/seq/Cargo.toml +++ b/src/uu/seq/Cargo.toml @@ -20,7 +20,7 @@ bigdecimal = { workspace = true } clap = { workspace = true } num-bigint = { workspace = true } num-traits = { workspace = true } -uucore = { workspace = true, features = ["memo"] } +uucore = { workspace = true, features = ["format"] } [[bin]] name = "seq" diff --git a/src/uu/seq/src/seq.rs b/src/uu/seq/src/seq.rs index 2e55efa4ada..0dd65fc3f24 100644 --- a/src/uu/seq/src/seq.rs +++ b/src/uu/seq/src/seq.rs @@ -4,15 +4,12 @@ // * file that was distributed with this source code. // spell-checker:ignore (ToDO) istr chiter argptr ilen extendedbigdecimal extendedbigint numberparse use std::io::{stdout, ErrorKind, Write}; -use std::process::exit; use clap::{crate_version, Arg, ArgAction, Command}; use num_traits::Zero; -use uucore::error::FromIo; use uucore::error::UResult; -use uucore::memo::printf; -use uucore::show; +use uucore::format::printf; use uucore::{format_usage, help_about, help_usage}; mod error; @@ -251,7 +248,7 @@ fn print_seq( pad: bool, padding: usize, format: Option<&str>, -) -> std::io::Result<()> { +) -> UResult<()> { let stdout = stdout(); let mut stdout = stdout.lock(); let (first, increment, last) = range; @@ -277,10 +274,7 @@ fn print_seq( match format { Some(f) => { let s = format!("{value}"); - if let Err(x) = printf(f, &[s]) { - show!(x); - exit(1); - } + printf(f, &[s])?; } None => write_value_float( &mut stdout, @@ -322,7 +316,7 @@ fn print_seq_integers( pad: bool, padding: usize, format: Option<&str>, -) -> std::io::Result<()> { +) -> UResult<()> { let stdout = stdout(); let mut stdout = stdout.lock(); let (first, increment, last) = range; @@ -342,10 +336,7 @@ fn print_seq_integers( match format { Some(f) => { let s = format!("{value}"); - if let Err(x) = printf(f, &[s]) { - show!(x); - exit(1); - } + printf(f, &[s])?; } None => write_value_int(&mut stdout, &value, padding, pad, is_first_iteration)?, } From 2bb56d44a4161b1b40225f27f8e30090910aae38 Mon Sep 17 00:00:00 2001 From: John Shin Date: Sat, 5 Aug 2023 18:23:29 -0700 Subject: [PATCH 0006/2851] seq: add tests for infinity and -infinity args --- src/uu/seq/src/numberparse.rs | 12 ++++++++++++ tests/by-util/test_seq.rs | 10 ++++++++++ 2 files changed, 22 insertions(+) diff --git a/src/uu/seq/src/numberparse.rs b/src/uu/seq/src/numberparse.rs index 1921e2e4c4e..156f80fb91f 100644 --- a/src/uu/seq/src/numberparse.rs +++ b/src/uu/seq/src/numberparse.rs @@ -477,11 +477,23 @@ mod tests { #[test] fn test_parse_inf() { assert_eq!(parse("inf"), Number::Float(ExtendedBigDecimal::Infinity)); + assert_eq!( + parse("infinity"), + Number::Float(ExtendedBigDecimal::Infinity) + ); assert_eq!(parse("+inf"), Number::Float(ExtendedBigDecimal::Infinity)); + assert_eq!( + parse("+infinity"), + Number::Float(ExtendedBigDecimal::Infinity) + ); assert_eq!( parse("-inf"), Number::Float(ExtendedBigDecimal::MinusInfinity) ); + assert_eq!( + parse("-infinity"), + Number::Float(ExtendedBigDecimal::MinusInfinity) + ); } #[test] diff --git a/tests/by-util/test_seq.rs b/tests/by-util/test_seq.rs index de078191251..95de2f3cd0a 100644 --- a/tests/by-util/test_seq.rs +++ b/tests/by-util/test_seq.rs @@ -619,11 +619,21 @@ fn test_neg_inf() { run(&["--", "-inf", "0"], b"-inf\n-inf\n-inf\n"); } +#[test] +fn test_neg_infinity() { + run(&["--", "-infinity", "0"], b"-inf\n-inf\n-inf\n"); +} + #[test] fn test_inf() { run(&["inf"], b"1\n2\n3\n"); } +#[test] +fn test_infinity() { + run(&["infinity"], b"1\n2\n3\n"); +} + #[test] fn test_inf_width() { run( From 407bccc54f48dc54ab28fdc10da0c4ecd9c5c805 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Thu, 10 Aug 2023 23:20:20 +0200 Subject: [PATCH 0007/2851] some more work on printf spec --- src/uucore/src/lib/features/format/spec.rs | 78 +++++++++++++++++----- 1 file changed, 60 insertions(+), 18 deletions(-) diff --git a/src/uucore/src/lib/features/format/spec.rs b/src/uucore/src/lib/features/format/spec.rs index 4319e44d93f..d1786c3d36f 100644 --- a/src/uucore/src/lib/features/format/spec.rs +++ b/src/uucore/src/lib/features/format/spec.rs @@ -358,20 +358,28 @@ impl Spec { return Err(FormatError::InvalidArgument(arg)); }; - match positive_sign { - PositiveSign::None => Ok(()), - PositiveSign::Plus => write!(writer, "+"), - PositiveSign::Space => write!(writer, " "), + if f.is_sign_positive() { + match positive_sign { + PositiveSign::None => Ok(()), + PositiveSign::Plus => write!(writer, "+"), + PositiveSign::Space => write!(writer, " "), + } + .map_err(FormatError::IoError)?; } - .map_err(FormatError::IoError)?; let s = match variant { - FloatVariant::Decimal => format_float_decimal(f, precision, case, force_decimal), + FloatVariant::Decimal => { + format_float_decimal(f, precision, case, force_decimal) + } FloatVariant::Scientific => { format_float_scientific(f, precision, case, force_decimal) } - FloatVariant::Shortest => format_float_shortest(f, precision, case, force_decimal), - FloatVariant::Hexadecimal => todo!(), + FloatVariant::Shortest => { + format_float_shortest(f, precision, case, force_decimal) + } + FloatVariant::Hexadecimal => { + format_float_hexadecimal(f, precision, case, force_decimal) + } }; match alignment { @@ -385,6 +393,15 @@ impl Spec { } } +fn format_float_nonfinite(f: f64, case: Case) -> String { + debug_assert!(!f.is_finite()); + let mut s = format!("{f}"); + if case == Case::Uppercase { + s.make_ascii_uppercase(); + } + return s; +} + fn format_float_decimal( f: f64, precision: usize, @@ -392,11 +409,7 @@ fn format_float_decimal( force_decimal: ForceDecimal, ) -> String { if !f.is_finite() { - let mut s = format!("{f}"); - if case == Case::Lowercase { - s.make_ascii_uppercase(); - } - return s; + return format_float_nonfinite(f, case); } if precision == 0 && force_decimal == ForceDecimal::Yes { @@ -414,11 +427,7 @@ fn format_float_scientific( ) -> String { // If the float is NaN, -Nan, Inf or -Inf, format like any other float if !f.is_finite() { - let mut s = format!("{f}"); - if case == Case::Lowercase { - s.make_ascii_uppercase(); - } - return s; + return format_float_nonfinite(f, case); } let exponent: i32 = f.log10().floor() as i32; @@ -458,6 +467,39 @@ fn format_float_shortest( } } +fn format_float_hexadecimal( + f: f64, + precision: usize, + case: Case, + force_decimal: ForceDecimal, +) -> String { + if !f.is_finite() { + return format_float_nonfinite(f, case); + } + + let (first_digit, mantissa, exponent) = if f == 0.0 { + (0, 0, 0) + } else { + let bits = f.to_bits(); + let exponent_bits = ((bits >> 52) & 0x7fff) as i64; + let exponent = exponent_bits - 1023; + let mantissa = bits & 0xf_ffff_ffff_ffff; + (1, mantissa, exponent) + }; + + let mut s = match (precision, force_decimal) { + (0, ForceDecimal::No) => format!("0x{first_digit}p{exponent:+x}"), + (0, ForceDecimal::Yes) => format!("0x{first_digit}.p{exponent:+x}"), + _ => format!("0x{first_digit}.{mantissa:0>13x}p{exponent:+x}") + }; + + if case == Case::Uppercase { + s.make_ascii_uppercase(); + } + + return s; +} + fn resolve_asterisk( option: Option>, args: impl Iterator, From 7d7d4fb04a6bd053c91cc8cdc469ca734000e60d Mon Sep 17 00:00:00 2001 From: zhitkoff Date: Tue, 22 Aug 2023 13:09:28 -0400 Subject: [PATCH 0008/2851] build-gnu.sh: `/usr/bin/timeout` should not be hardcoded to /usr/bin location Fixes #5193 --- util/build-gnu.sh | 32 ++++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/util/build-gnu.sh b/util/build-gnu.sh index d852ed66fb6..d02c8c842e6 100755 --- a/util/build-gnu.sh +++ b/util/build-gnu.sh @@ -82,7 +82,16 @@ else ./bootstrap --skip-po ./configure --quiet --disable-gcc-warnings #Add timeout to to protect against hangs - sed -i 's|^"\$@|/usr/bin/timeout 600 "\$@|' build-aux/test-driver + # On MacOS there is no system /usr/bin/timeout + # and trying to add it to /usr/bin (with symlink of copy binary) will fail unless system integrity protection is disabled (not ideal) + # ref: https://support.apple.com/en-us/102149 + # On MacOS the Homebrew coreutils could be installed and then "sudo ln -s /opt/homebrew/bin/timeout /usr/local/bin/timeout" + # Set to /usr/local/timeout instead if /usr/bin/timeout is not found + if [ -x /usr/bin/timeout ] ; then + sed -i 's|^"\$@|/usr/bin/timeout 600 "\$@|' build-aux/test-driver + else + sed -i 's|^"\$@|/usr/local/bin/timeout 600 "\$@|' build-aux/test-driver + fi # Change the PATH in the Makefile to test the uutils coreutils instead of the GNU coreutils sed -i "s/^[[:blank:]]*PATH=.*/ PATH='${UU_BUILD_DIR//\//\\/}\$(PATH_SEPARATOR)'\"\$\$PATH\" \\\/" Makefile sed -i 's| tr | /usr/bin/tr |' tests/init.sh @@ -153,13 +162,28 @@ sed -i 's|touch |/usr/bin/touch |' tests/cp/reflink-perm.sh tests/ls/block-size. sed -i 's|ln -|/usr/bin/ln -|' tests/cp/link-deref.sh sed -i 's|cp |/usr/bin/cp |' tests/mv/hard-2.sh sed -i 's|paste |/usr/bin/paste |' tests/misc/od-endian.sh -sed -i 's|timeout |/usr/bin/timeout |' tests/tail-2/follow-stdin.sh +# On MacOS there is no system /usr/bin/timeout +# and trying to add it to /usr/bin (with symlink of copy binary) will fail unless system integrity protection is disabled (not ideal) +# ref: https://support.apple.com/en-us/102149 +# On MacOS the Homebrew coreutils could be installed and then "sudo ln -s /opt/homebrew/bin/timeout /usr/local/bin/timeout" +# Set to /usr/local/timeout instead if /usr/bin/timeout is not found +if [ -x /usr/bin/timeout ] ; then + sed -i 's|timeout |/usr/bin/timeout |' tests/tail-2/follow-stdin.sh +else + sed -i 's|timeout |/usr/local/bin/timeout |' tests/tail-2/follow-stdin.sh +fi + # Add specific timeout to tests that currently hang to limit time spent waiting -sed -i 's|\(^\s*\)seq \$|\1/usr/bin/timeout 0.1 seq \$|' tests/misc/seq-precision.sh tests/misc/seq-long-double.sh +if [ -x /usr/bin/timeout ] ; then + sed -i 's|\(^\s*\)seq \$|\1/usr/bin/timeout 0.1 seq \$|' tests/misc/seq-precision.sh tests/misc/seq-long-double.sh +else + sed -i 's|\(^\s*\)seq \$|\1/usr/local/bin/timeout 0.1 seq \$|' tests/misc/seq-precision.sh tests/misc/seq-long-double.sh +fi -# Remove dup of /usr/bin/ when executed several times +# Remove dup of /usr/bin/ (and /usr/local/bin) when executed several times grep -rlE '/usr/bin/\s?/usr/bin' init.cfg tests/* | xargs --no-run-if-empty sed -Ei 's|/usr/bin/\s?/usr/bin/|/usr/bin/|g' +grep -rlE '/usr/local/bin/\s?/usr/local/bin' init.cfg tests/* | xargs --no-run-if-empty sed -Ei 's|/usr/local/bin/\s?/usr/local/bin/|/usr/local/bin/|g' #### Adjust tests to make them work with Rust/coreutils # in some cases, what we are doing in rust/coreutils is good (or better) From 75cc6e3fdccedf551e297e54715cc65ed6045c7c Mon Sep 17 00:00:00 2001 From: zhitkoff Date: Tue, 22 Aug 2023 13:22:24 -0400 Subject: [PATCH 0009/2851] added some TODO(s) for missing/moved locations --- util/build-gnu.sh | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/util/build-gnu.sh b/util/build-gnu.sh index d02c8c842e6..6c608a06077 100755 --- a/util/build-gnu.sh +++ b/util/build-gnu.sh @@ -151,6 +151,9 @@ sed -i -e '/tests\/misc\/seq-precision.sh/ D' \ sed -i '/INT_OFLOW/ D' tests/misc/printf.sh # Use the system coreutils where the test fails due to error in a util that is not the one being tested +# TODO : tests/tail-2/ does not appear to exist +# and have been moved to just tests/tail/ location +# Might need to update the section bvelow to reflect that sed -i 's|stat|/usr/bin/stat|' tests/touch/60-seconds.sh tests/misc/sort-compress-proc.sh sed -i 's|ls -|/usr/bin/ls -|' tests/cp/same-file.sh tests/misc/mknod.sh tests/mv/part-symlink.sh sed -i 's|chmod |/usr/bin/chmod |' tests/du/inacc-dir.sh tests/tail-2/tail-n0f.sh tests/cp/fail-perm.sh tests/mv/i-2.sh tests/misc/shuf.sh @@ -175,6 +178,9 @@ fi # Add specific timeout to tests that currently hang to limit time spent waiting +# TODO : tests/misc/seq-precision.sh tests/misc/seq-long-double.sh do not appear to exist +# and have been moved to tests/seq/ location +# Might need to update the section bvelow to reflect that if [ -x /usr/bin/timeout ] ; then sed -i 's|\(^\s*\)seq \$|\1/usr/bin/timeout 0.1 seq \$|' tests/misc/seq-precision.sh tests/misc/seq-long-double.sh else @@ -205,6 +211,9 @@ sed -i -e "s|rm: cannot remove 'rel': Permission denied|rm: cannot remove 'rel': # overlay-headers.sh test intends to check for inotify events, # however there's a bug because `---dis` is an alias for: `---disable-inotify` +# TODO : tests/tail-2/ does not appear to exist +# and have been moved to just tests/tail/ location +# Might need to update the section bvelow to reflect that sed -i -e "s|---dis ||g" tests/tail-2/overlay-headers.sh test -f "${UU_BUILD_DIR}/getlimits" || cp src/getlimits "${UU_BUILD_DIR}" From c44c3cd71649252ae333dca0c55ab56d9ca52163 Mon Sep 17 00:00:00 2001 From: zhitkoff Date: Tue, 22 Aug 2023 15:28:02 -0400 Subject: [PATCH 0010/2851] fixed spelling --- util/build-gnu.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/util/build-gnu.sh b/util/build-gnu.sh index 6c608a06077..4414b7f12ba 100755 --- a/util/build-gnu.sh +++ b/util/build-gnu.sh @@ -153,7 +153,7 @@ sed -i '/INT_OFLOW/ D' tests/misc/printf.sh # Use the system coreutils where the test fails due to error in a util that is not the one being tested # TODO : tests/tail-2/ does not appear to exist # and have been moved to just tests/tail/ location -# Might need to update the section bvelow to reflect that +# Might need to update the section below to reflect that sed -i 's|stat|/usr/bin/stat|' tests/touch/60-seconds.sh tests/misc/sort-compress-proc.sh sed -i 's|ls -|/usr/bin/ls -|' tests/cp/same-file.sh tests/misc/mknod.sh tests/mv/part-symlink.sh sed -i 's|chmod |/usr/bin/chmod |' tests/du/inacc-dir.sh tests/tail-2/tail-n0f.sh tests/cp/fail-perm.sh tests/mv/i-2.sh tests/misc/shuf.sh @@ -180,7 +180,7 @@ fi # Add specific timeout to tests that currently hang to limit time spent waiting # TODO : tests/misc/seq-precision.sh tests/misc/seq-long-double.sh do not appear to exist # and have been moved to tests/seq/ location -# Might need to update the section bvelow to reflect that +# Might need to update the section below to reflect that if [ -x /usr/bin/timeout ] ; then sed -i 's|\(^\s*\)seq \$|\1/usr/bin/timeout 0.1 seq \$|' tests/misc/seq-precision.sh tests/misc/seq-long-double.sh else @@ -213,7 +213,7 @@ sed -i -e "s|rm: cannot remove 'rel': Permission denied|rm: cannot remove 'rel': # however there's a bug because `---dis` is an alias for: `---disable-inotify` # TODO : tests/tail-2/ does not appear to exist # and have been moved to just tests/tail/ location -# Might need to update the section bvelow to reflect that +# Might need to update the section below to reflect that sed -i -e "s|---dis ||g" tests/tail-2/overlay-headers.sh test -f "${UU_BUILD_DIR}/getlimits" || cp src/getlimits "${UU_BUILD_DIR}" From 6468845850a0c847f42fb400ffecbdcf54f8e99c Mon Sep 17 00:00:00 2001 From: zhitkoff Date: Thu, 24 Aug 2023 20:36:52 -0400 Subject: [PATCH 0011/2851] refactor to check for system timeout once + commented out many moved/deleted test files that make script fail --- util/build-gnu.sh | 71 ++++++++++++++++++++++------------------------- 1 file changed, 33 insertions(+), 38 deletions(-) diff --git a/util/build-gnu.sh b/util/build-gnu.sh index 4414b7f12ba..c6991d15c1b 100755 --- a/util/build-gnu.sh +++ b/util/build-gnu.sh @@ -18,6 +18,20 @@ path_GNU="$(readlink -fm -- "${path_GNU:-${path_UUTILS}/../gnu}")" ### +# On MacOS there is no system /usr/bin/timeout +# and trying to add it to /usr/bin (with symlink of copy binary) will fail unless system integrity protection is disabled (not ideal) +# ref: https://support.apple.com/en-us/102149 +# On MacOS the Homebrew coreutils could be installed and then "sudo ln -s /opt/homebrew/bin/timeout /usr/local/bin/timeout" +# Set to /usr/local/bin/timeout instead if /usr/bin/timeout is not found +SYSTEM_TIMEOUT="timeout" +if [ -x /usr/bin/timeout ] ; then + SYSTEM_TIMEOUT="/usr/bin/timeout" +elif [ -x /usr/local/bin/timeout ] ; then + SYSTEM_TIMEOUT="/usr/local/bin/timeout" +fi + +### + if test ! -d "${path_GNU}"; then echo "Could not find GNU coreutils (expected at '${path_GNU}')" echo "Run the following to download into the expected path:" @@ -82,16 +96,7 @@ else ./bootstrap --skip-po ./configure --quiet --disable-gcc-warnings #Add timeout to to protect against hangs - # On MacOS there is no system /usr/bin/timeout - # and trying to add it to /usr/bin (with symlink of copy binary) will fail unless system integrity protection is disabled (not ideal) - # ref: https://support.apple.com/en-us/102149 - # On MacOS the Homebrew coreutils could be installed and then "sudo ln -s /opt/homebrew/bin/timeout /usr/local/bin/timeout" - # Set to /usr/local/timeout instead if /usr/bin/timeout is not found - if [ -x /usr/bin/timeout ] ; then - sed -i 's|^"\$@|/usr/bin/timeout 600 "\$@|' build-aux/test-driver - else - sed -i 's|^"\$@|/usr/local/bin/timeout 600 "\$@|' build-aux/test-driver - fi + sed -i 's|^"\$@|'"${SYSTEM_TIMEOUT}"' 600 "\$@|' build-aux/test-driver # Change the PATH in the Makefile to test the uutils coreutils instead of the GNU coreutils sed -i "s/^[[:blank:]]*PATH=.*/ PATH='${UU_BUILD_DIR//\//\\/}\$(PATH_SEPARATOR)'\"\$\$PATH\" \\\/" Makefile sed -i 's| tr | /usr/bin/tr |' tests/init.sh @@ -148,44 +153,32 @@ sed -i -e '/tests\/misc\/seq-precision.sh/ D' \ Makefile # printf doesn't limit the values used in its arg, so this produced ~2GB of output -sed -i '/INT_OFLOW/ D' tests/misc/printf.sh +# Looks like tests/misc/printf.sh does not exist anymore - comment it out for now +#sed -i '/INT_OFLOW/ D' tests/misc/printf.sh # Use the system coreutils where the test fails due to error in a util that is not the one being tested # TODO : tests/tail-2/ does not appear to exist # and have been moved to just tests/tail/ location # Might need to update the section below to reflect that -sed -i 's|stat|/usr/bin/stat|' tests/touch/60-seconds.sh tests/misc/sort-compress-proc.sh +# Also looks like tests/misc/sort-compress-proc.sh and tests/tail-2/tail-n0f.sh and tests/misc/shuf.sh and many others do not exist anymore or moved - comment it out for now +sed -i 's|stat|/usr/bin/stat|' tests/touch/60-seconds.sh #tests/misc/sort-compress-proc.sh sed -i 's|ls -|/usr/bin/ls -|' tests/cp/same-file.sh tests/misc/mknod.sh tests/mv/part-symlink.sh -sed -i 's|chmod |/usr/bin/chmod |' tests/du/inacc-dir.sh tests/tail-2/tail-n0f.sh tests/cp/fail-perm.sh tests/mv/i-2.sh tests/misc/shuf.sh -sed -i 's|sort |/usr/bin/sort |' tests/ls/hyperlink.sh tests/misc/test-N.sh -sed -i 's|split |/usr/bin/split |' tests/misc/factor-parallel.sh -sed -i 's|id -|/usr/bin/id -|' tests/misc/runcon-no-reorder.sh +sed -i 's|chmod |/usr/bin/chmod |' tests/du/inacc-dir.sh tests/cp/fail-perm.sh tests/mv/i-2.sh #tests/misc/shuf.sh #tests/tail-2/tail-n0f.sh +sed -i 's|sort |/usr/bin/sort |' tests/ls/hyperlink.sh #tests/misc/test-N.sh +#sed -i 's|split |/usr/bin/split |' tests/misc/factor-parallel.sh +#sed -i 's|id -|/usr/bin/id -|' tests/misc/runcon-no-reorder.sh # tests/ls/abmon-align.sh - https://github.com/uutils/coreutils/issues/3505 -sed -i 's|touch |/usr/bin/touch |' tests/cp/reflink-perm.sh tests/ls/block-size.sh tests/mv/update.sh tests/misc/ls-time.sh tests/misc/stat-nanoseconds.sh tests/misc/time-style.sh tests/misc/test-N.sh tests/ls/abmon-align.sh +sed -i 's|touch |/usr/bin/touch |' tests/cp/reflink-perm.sh tests/ls/block-size.sh tests/mv/update.sh tests/misc/time-style.sh tests/ls/abmon-align.sh #tests/misc/ls-time.sh tests/misc/stat-nanoseconds.sh tests/misc/test-N.sh sed -i 's|ln -|/usr/bin/ln -|' tests/cp/link-deref.sh sed -i 's|cp |/usr/bin/cp |' tests/mv/hard-2.sh -sed -i 's|paste |/usr/bin/paste |' tests/misc/od-endian.sh -# On MacOS there is no system /usr/bin/timeout -# and trying to add it to /usr/bin (with symlink of copy binary) will fail unless system integrity protection is disabled (not ideal) -# ref: https://support.apple.com/en-us/102149 -# On MacOS the Homebrew coreutils could be installed and then "sudo ln -s /opt/homebrew/bin/timeout /usr/local/bin/timeout" -# Set to /usr/local/timeout instead if /usr/bin/timeout is not found -if [ -x /usr/bin/timeout ] ; then - sed -i 's|timeout |/usr/bin/timeout |' tests/tail-2/follow-stdin.sh -else - sed -i 's|timeout |/usr/local/bin/timeout |' tests/tail-2/follow-stdin.sh -fi - +#sed -i 's|paste |/usr/bin/paste |' tests/misc/od-endian.sh +#sed -i 's|timeout |'"${SYSTEM_TIMEOUT}"' |' tests/tail-2/follow-stdin.sh # Add specific timeout to tests that currently hang to limit time spent waiting # TODO : tests/misc/seq-precision.sh tests/misc/seq-long-double.sh do not appear to exist # and have been moved to tests/seq/ location -# Might need to update the section below to reflect that -if [ -x /usr/bin/timeout ] ; then - sed -i 's|\(^\s*\)seq \$|\1/usr/bin/timeout 0.1 seq \$|' tests/misc/seq-precision.sh tests/misc/seq-long-double.sh -else - sed -i 's|\(^\s*\)seq \$|\1/usr/local/bin/timeout 0.1 seq \$|' tests/misc/seq-precision.sh tests/misc/seq-long-double.sh -fi +# Might need to update the section below to reflect that, but comment it out for now +#sed -i 's|\(^\s*\)seq \$|\1'"${SYSTEM_TIMEOUT}"' 0.1 seq \$|' tests/misc/seq-precision.sh tests/misc/seq-long-double.sh # Remove dup of /usr/bin/ (and /usr/local/bin) when executed several times grep -rlE '/usr/bin/\s?/usr/bin' init.cfg tests/* | xargs --no-run-if-empty sed -Ei 's|/usr/bin/\s?/usr/bin/|/usr/bin/|g' @@ -214,7 +207,7 @@ sed -i -e "s|rm: cannot remove 'rel': Permission denied|rm: cannot remove 'rel': # TODO : tests/tail-2/ does not appear to exist # and have been moved to just tests/tail/ location # Might need to update the section below to reflect that -sed -i -e "s|---dis ||g" tests/tail-2/overlay-headers.sh +#sed -i -e "s|---dis ||g" tests/tail-2/overlay-headers.sh test -f "${UU_BUILD_DIR}/getlimits" || cp src/getlimits "${UU_BUILD_DIR}" @@ -271,11 +264,13 @@ sed -i -e "s/Try 'mv --help' for more information/For more information, try '--h # GNU doesn't support width > INT_MAX # disable these test cases -sed -i -E "s|^([^#]*2_31.*)$|#\1|g" tests/misc/printf-cov.pl +# TODO: moved or deleted tests/misc/printf-cov.pl +#sed -i -E "s|^([^#]*2_31.*)$|#\1|g" tests/misc/printf-cov.pl sed -i -e "s/du: invalid -t argument/du: invalid --threshold argument/" -e "s/du: option requires an argument/error: a value is required for '--threshold ' but none was supplied/" -e "/Try 'du --help' for more information./d" tests/du/threshold.sh # disable two kind of tests: # "hostid BEFORE --help" doesn't fail for GNU. we fail. we are probably doing better # "hostid BEFORE --help AFTER " same for this -sed -i -e "s/env \$prog \$BEFORE \$opt > out2/env \$prog \$BEFORE \$opt > out2 #/" -e "s/env \$prog \$BEFORE \$opt AFTER > out3/env \$prog \$BEFORE \$opt AFTER > out3 #/" -e "s/compare exp out2/compare exp out2 #/" -e "s/compare exp out3/compare exp out3 #/" tests/misc/help-version-getopt.sh +# TODO moved or deleted tests/misc/help-version-getopt.sh +#sed -i -e "s/env \$prog \$BEFORE \$opt > out2/env \$prog \$BEFORE \$opt > out2 #/" -e "s/env \$prog \$BEFORE \$opt AFTER > out3/env \$prog \$BEFORE \$opt AFTER > out3 #/" -e "s/compare exp out2/compare exp out2 #/" -e "s/compare exp out3/compare exp out3 #/" tests/misc/help-version-getopt.sh From 350b9c3d486449f592b9fd92f349b884926073cb Mon Sep 17 00:00:00 2001 From: zhitkoff Date: Fri, 25 Aug 2023 13:43:48 -0400 Subject: [PATCH 0012/2851] reverted commented out test files and added more details in TODO(s) --- util/build-gnu.sh | 51 +++++++++++++++++++++-------------------------- 1 file changed, 23 insertions(+), 28 deletions(-) diff --git a/util/build-gnu.sh b/util/build-gnu.sh index c6991d15c1b..587cf858096 100755 --- a/util/build-gnu.sh +++ b/util/build-gnu.sh @@ -152,37 +152,37 @@ sed -i -e '/tests\/misc\/invalid-opt.pl/ D' \ sed -i -e '/tests\/misc\/seq-precision.sh/ D' \ Makefile +# TODO: many files and some directories modified with 'sed' in the sections below either no longer exist or have been moved +# TODO: Might need to review and updated sections below +# TODO: As a result this script will fail when executed normally as 'bash util/build-gnu.hs' due to the 'set -e' set at the beginning +# TODO: The rest of the 'sed' commands after first failure in this scenario will not be executed as bash will exit on first error +# TODO: However, the behaviour might be different when running it via GitHub actions (GnuTests) +# TODO: For now, when running in local a workaround would be to comment out the 'set -e' at the beginning of the file + # printf doesn't limit the values used in its arg, so this produced ~2GB of output -# Looks like tests/misc/printf.sh does not exist anymore - comment it out for now -#sed -i '/INT_OFLOW/ D' tests/misc/printf.sh +# TODO: this is the first one to likely fail as tests/misc/printf.sh does not exist/have been moved +sed -i '/INT_OFLOW/ D' tests/misc/printf.sh +# TODO: all commands below might not be executed # Use the system coreutils where the test fails due to error in a util that is not the one being tested -# TODO : tests/tail-2/ does not appear to exist -# and have been moved to just tests/tail/ location -# Might need to update the section below to reflect that -# Also looks like tests/misc/sort-compress-proc.sh and tests/tail-2/tail-n0f.sh and tests/misc/shuf.sh and many others do not exist anymore or moved - comment it out for now -sed -i 's|stat|/usr/bin/stat|' tests/touch/60-seconds.sh #tests/misc/sort-compress-proc.sh +sed -i 's|stat|/usr/bin/stat|' tests/touch/60-seconds.sh tests/misc/sort-compress-proc.sh sed -i 's|ls -|/usr/bin/ls -|' tests/cp/same-file.sh tests/misc/mknod.sh tests/mv/part-symlink.sh -sed -i 's|chmod |/usr/bin/chmod |' tests/du/inacc-dir.sh tests/cp/fail-perm.sh tests/mv/i-2.sh #tests/misc/shuf.sh #tests/tail-2/tail-n0f.sh -sed -i 's|sort |/usr/bin/sort |' tests/ls/hyperlink.sh #tests/misc/test-N.sh -#sed -i 's|split |/usr/bin/split |' tests/misc/factor-parallel.sh -#sed -i 's|id -|/usr/bin/id -|' tests/misc/runcon-no-reorder.sh +sed -i 's|chmod |/usr/bin/chmod |' tests/du/inacc-dir.sh tests/tail-2/tail-n0f.sh tests/cp/fail-perm.sh tests/mv/i-2.sh tests/misc/shuf.sh +sed -i 's|sort |/usr/bin/sort |' tests/ls/hyperlink.sh tests/misc/test-N.sh +sed -i 's|split |/usr/bin/split |' tests/misc/factor-parallel.sh +sed -i 's|id -|/usr/bin/id -|' tests/misc/runcon-no-reorder.sh # tests/ls/abmon-align.sh - https://github.com/uutils/coreutils/issues/3505 -sed -i 's|touch |/usr/bin/touch |' tests/cp/reflink-perm.sh tests/ls/block-size.sh tests/mv/update.sh tests/misc/time-style.sh tests/ls/abmon-align.sh #tests/misc/ls-time.sh tests/misc/stat-nanoseconds.sh tests/misc/test-N.sh +sed -i 's|touch |/usr/bin/touch |' tests/cp/reflink-perm.sh tests/ls/block-size.sh tests/mv/update.sh tests/misc/ls-time.sh tests/misc/stat-nanoseconds.sh tests/misc/time-style.sh tests/misc/test-N.sh tests/ls/abmon-align.sh sed -i 's|ln -|/usr/bin/ln -|' tests/cp/link-deref.sh sed -i 's|cp |/usr/bin/cp |' tests/mv/hard-2.sh -#sed -i 's|paste |/usr/bin/paste |' tests/misc/od-endian.sh -#sed -i 's|timeout |'"${SYSTEM_TIMEOUT}"' |' tests/tail-2/follow-stdin.sh +sed -i 's|paste |/usr/bin/paste |' tests/misc/od-endian.sh +sed -i 's|timeout |/usr/bin/timeout |' tests/tail-2/follow-stdin.sh # Add specific timeout to tests that currently hang to limit time spent waiting -# TODO : tests/misc/seq-precision.sh tests/misc/seq-long-double.sh do not appear to exist -# and have been moved to tests/seq/ location -# Might need to update the section below to reflect that, but comment it out for now -#sed -i 's|\(^\s*\)seq \$|\1'"${SYSTEM_TIMEOUT}"' 0.1 seq \$|' tests/misc/seq-precision.sh tests/misc/seq-long-double.sh +sed -i 's|\(^\s*\)seq \$|\1/usr/bin/timeout 0.1 seq \$|' tests/misc/seq-precision.sh tests/misc/seq-long-double.sh -# Remove dup of /usr/bin/ (and /usr/local/bin) when executed several times +# Remove dup of /usr/bin/ when executed several times grep -rlE '/usr/bin/\s?/usr/bin' init.cfg tests/* | xargs --no-run-if-empty sed -Ei 's|/usr/bin/\s?/usr/bin/|/usr/bin/|g' -grep -rlE '/usr/local/bin/\s?/usr/local/bin' init.cfg tests/* | xargs --no-run-if-empty sed -Ei 's|/usr/local/bin/\s?/usr/local/bin/|/usr/local/bin/|g' #### Adjust tests to make them work with Rust/coreutils # in some cases, what we are doing in rust/coreutils is good (or better) @@ -204,10 +204,7 @@ sed -i -e "s|rm: cannot remove 'rel': Permission denied|rm: cannot remove 'rel': # overlay-headers.sh test intends to check for inotify events, # however there's a bug because `---dis` is an alias for: `---disable-inotify` -# TODO : tests/tail-2/ does not appear to exist -# and have been moved to just tests/tail/ location -# Might need to update the section below to reflect that -#sed -i -e "s|---dis ||g" tests/tail-2/overlay-headers.sh +sed -i -e "s|---dis ||g" tests/tail-2/overlay-headers.sh test -f "${UU_BUILD_DIR}/getlimits" || cp src/getlimits "${UU_BUILD_DIR}" @@ -264,13 +261,11 @@ sed -i -e "s/Try 'mv --help' for more information/For more information, try '--h # GNU doesn't support width > INT_MAX # disable these test cases -# TODO: moved or deleted tests/misc/printf-cov.pl -#sed -i -E "s|^([^#]*2_31.*)$|#\1|g" tests/misc/printf-cov.pl +sed -i -E "s|^([^#]*2_31.*)$|#\1|g" tests/misc/printf-cov.pl sed -i -e "s/du: invalid -t argument/du: invalid --threshold argument/" -e "s/du: option requires an argument/error: a value is required for '--threshold ' but none was supplied/" -e "/Try 'du --help' for more information./d" tests/du/threshold.sh # disable two kind of tests: # "hostid BEFORE --help" doesn't fail for GNU. we fail. we are probably doing better # "hostid BEFORE --help AFTER " same for this -# TODO moved or deleted tests/misc/help-version-getopt.sh -#sed -i -e "s/env \$prog \$BEFORE \$opt > out2/env \$prog \$BEFORE \$opt > out2 #/" -e "s/env \$prog \$BEFORE \$opt AFTER > out3/env \$prog \$BEFORE \$opt AFTER > out3 #/" -e "s/compare exp out2/compare exp out2 #/" -e "s/compare exp out3/compare exp out3 #/" tests/misc/help-version-getopt.sh +sed -i -e "s/env \$prog \$BEFORE \$opt > out2/env \$prog \$BEFORE \$opt > out2 #/" -e "s/env \$prog \$BEFORE \$opt AFTER > out3/env \$prog \$BEFORE \$opt AFTER > out3 #/" -e "s/compare exp out2/compare exp out2 #/" -e "s/compare exp out3/compare exp out3 #/" tests/misc/help-version-getopt.sh From db342556c2ce93be472abccd6594b27e932aa7f6 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Mon, 14 Aug 2023 13:40:05 +0200 Subject: [PATCH 0013/2851] nl: implement -d/--section-delimiter --- src/uu/nl/src/helper.rs | 9 +++++ src/uu/nl/src/nl.rs | 44 ++++++++++++++++----- tests/by-util/test_nl.rs | 83 +++++++++++++++++++++++++++++++++++++++- 3 files changed, 125 insertions(+), 11 deletions(-) diff --git a/src/uu/nl/src/helper.rs b/src/uu/nl/src/helper.rs index fe550e6a063..ae14a6d59cc 100644 --- a/src/uu/nl/src/helper.rs +++ b/src/uu/nl/src/helper.rs @@ -13,6 +13,15 @@ pub fn parse_options(settings: &mut crate::Settings, opts: &clap::ArgMatches) -> // This vector holds error messages encountered. let mut errs: Vec = vec![]; settings.renumber = opts.get_flag(options::NO_RENUMBER); + if let Some(delimiter) = opts.get_one::(options::SECTION_DELIMITER) { + // check whether the delimiter is a single ASCII char (1 byte) + // because GNU nl doesn't add a ':' to single non-ASCII chars + settings.section_delimiter = if delimiter.len() == 1 { + format!("{delimiter}:") + } else { + delimiter.to_owned() + }; + } if let Some(val) = opts.get_one::(options::NUMBER_SEPARATOR) { settings.number_separator = val.to_owned(); } diff --git a/src/uu/nl/src/nl.rs b/src/uu/nl/src/nl.rs index 61a0a9f35f4..839ec35db0f 100644 --- a/src/uu/nl/src/nl.rs +++ b/src/uu/nl/src/nl.rs @@ -23,7 +23,7 @@ pub struct Settings { body_numbering: NumberingStyle, footer_numbering: NumberingStyle, // The variable corresponding to -d - section_delimiter: [char; 2], + section_delimiter: String, // The variables corresponding to the options -v, -i, -l, -w. starting_line_number: i64, line_increment: i64, @@ -43,7 +43,7 @@ impl Default for Settings { header_numbering: NumberingStyle::None, body_numbering: NumberingStyle::NonEmpty, footer_numbering: NumberingStyle::None, - section_delimiter: ['\\', ':'], + section_delimiter: String::from("\\:"), starting_line_number: 1, line_increment: 1, join_blank_lines: 1, @@ -120,6 +120,32 @@ impl NumberFormat { } } +enum SectionDelimiter { + Header, + Body, + Footer, +} + +impl SectionDelimiter { + // A valid section delimiter contains the pattern one to three times, + // and nothing else. + fn parse(s: &str, pattern: &str) -> Option { + if s.is_empty() || pattern.is_empty() { + return None; + } + + let pattern_count = s.matches(pattern).count(); + let is_length_ok = pattern_count * pattern.len() == s.len(); + + match (pattern_count, is_length_ok) { + (3, true) => Some(Self::Header), + (2, true) => Some(Self::Body), + (1, true) => Some(Self::Footer), + _ => None, + } + } +} + pub mod options { pub const HELP: &str = "help"; pub const FILE: &str = "file"; @@ -299,14 +325,12 @@ fn nl(reader: &mut BufReader, settings: &Settings) -> UResult<()> { consecutive_empty_lines = 0; }; - // FIXME section delimiters are hardcoded and settings.section_delimiter is ignored - // because --section-delimiter is not correctly implemented yet - let _ = settings.section_delimiter; // XXX suppress "field never read" warning - let new_numbering_style = match line.as_str() { - "\\:\\:\\:" => Some(&settings.header_numbering), - "\\:\\:" => Some(&settings.body_numbering), - "\\:" => Some(&settings.footer_numbering), - _ => None, + let new_numbering_style = match SectionDelimiter::parse(&line, &settings.section_delimiter) + { + Some(SectionDelimiter::Header) => Some(&settings.header_numbering), + Some(SectionDelimiter::Body) => Some(&settings.body_numbering), + Some(SectionDelimiter::Footer) => Some(&settings.footer_numbering), + None => None, }; if let Some(new_style) = new_numbering_style { diff --git a/tests/by-util/test_nl.rs b/tests/by-util/test_nl.rs index b58c0c206fb..e21bb503721 100644 --- a/tests/by-util/test_nl.rs +++ b/tests/by-util/test_nl.rs @@ -2,7 +2,8 @@ // // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// spell-checker:ignore binvalid finvalid hinvalid iinvalid linvalid ninvalid vinvalid winvalid +// +// spell-checker:ignore binvalid finvalid hinvalid iinvalid linvalid nabcabc nabcabcabc ninvalid vinvalid winvalid use crate::common::util::TestScenario; #[test] @@ -486,3 +487,83 @@ fn test_line_number_overflow() { .stdout_is(format!("{}\ta\n", i64::MIN)) .stderr_is("nl: line number overflow\n"); } + +#[test] +fn test_section_delimiter() { + for arg in ["-dabc", "--section-delimiter=abc"] { + new_ucmd!() + .arg(arg) + .pipe_in("a\nabcabcabc\nb") // header section + .succeeds() + .stdout_is(" 1\ta\n\n b\n"); + + new_ucmd!() + .arg(arg) + .pipe_in("a\nabcabc\nb") // body section + .succeeds() + .stdout_is(" 1\ta\n\n 1\tb\n"); + + new_ucmd!() + .arg(arg) + .pipe_in("a\nabc\nb") // footer section + .succeeds() + .stdout_is(" 1\ta\n\n b\n"); + } +} + +#[test] +fn test_one_char_section_delimiter_expansion() { + for arg in ["-da", "--section-delimiter=a"] { + new_ucmd!() + .arg(arg) + .pipe_in("a\na:a:a:\nb") // header section + .succeeds() + .stdout_is(" 1\ta\n\n b\n"); + + new_ucmd!() + .arg(arg) + .pipe_in("a\na:a:\nb") // body section + .succeeds() + .stdout_is(" 1\ta\n\n 1\tb\n"); + + new_ucmd!() + .arg(arg) + .pipe_in("a\na:\nb") // footer section + .succeeds() + .stdout_is(" 1\ta\n\n b\n"); + } +} + +#[test] +fn test_non_ascii_one_char_section_delimiter() { + for arg in ["-dä", "--section-delimiter=ä"] { + new_ucmd!() + .arg(arg) + .pipe_in("a\näää\nb") // header section + .succeeds() + .stdout_is(" 1\ta\n\n b\n"); + + new_ucmd!() + .arg(arg) + .pipe_in("a\nää\nb") // body section + .succeeds() + .stdout_is(" 1\ta\n\n 1\tb\n"); + + new_ucmd!() + .arg(arg) + .pipe_in("a\nä\nb") // footer section + .succeeds() + .stdout_is(" 1\ta\n\n b\n"); + } +} + +#[test] +fn test_empty_section_delimiter() { + for arg in ["-d ''", "--section-delimiter=''"] { + new_ucmd!() + .arg(arg) + .pipe_in("a\n\nb") + .succeeds() + .stdout_is(" 1\ta\n \n 2\tb\n"); + } +} From 84d96f9d028a4dd8c0f98ba6db2ba6dc90c79dcb Mon Sep 17 00:00:00 2001 From: zhitkoff Date: Sat, 26 Aug 2023 11:11:46 -0400 Subject: [PATCH 0014/2851] split: refactor for more common use case --- src/uu/split/src/split.rs | 81 ++++++++++++++++++- tests/by-util/test_split.rs | 152 +++++++++++++++++++++++++++++++++++- 2 files changed, 229 insertions(+), 4 deletions(-) diff --git a/src/uu/split/src/split.rs b/src/uu/split/src/split.rs index 71aacfed006..c4b7e6d660e 100644 --- a/src/uu/split/src/split.rs +++ b/src/uu/split/src/split.rs @@ -51,14 +51,66 @@ const AFTER_HELP: &str = help_section!("after help", "split.md"); #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { + let args = args.collect_lossy(); + + let (args, obs_lines) = handle_obsolete(&args[..]); + let matches = uu_app().try_get_matches_from(args)?; - match Settings::from(&matches) { + + match Settings::from(&matches, &obs_lines) { Ok(settings) => split(&settings), Err(e) if e.requires_usage() => Err(UUsageError::new(1, format!("{e}"))), Err(e) => Err(USimpleError::new(1, format!("{e}"))), } } +/// Extract obsolete shorthand (if any) for specifying lines in following scenarios (and similar) +/// `split -22 file` would mean `split -l 22 file` +/// `split -2de file` would mean `split -l 2 -d -e file` +/// `split -x300e file` would mean `split -x -l 300 -e file` +/// `split -x300e -22 file` would mean `split -x -e -l 22 file` (last obsolete lines option wins) +/// following GNU `split` behavior +fn handle_obsolete(args: &[String]) -> (Vec, Option) { + let mut v: Vec = vec![]; + let mut obs_lines = None; + for arg in args.iter() { + let slice = &arg; + if slice.starts_with('-') && !slice.starts_with("--") { + // start of the short option string + // extract numeric part and filter it out + let mut obs_lines_extracted: Vec = vec![]; + let filtered_slice: Vec = slice + .chars() + .filter(|c| { + if c.is_ascii_digit() { + obs_lines_extracted.push(*c); + false + } else { + true + } + }) + .collect(); + + if filtered_slice.get(1).is_some() { + // there were some short options in front of obsolete lines number + // i.e. '-xd100' or similar + // preserve it + v.push(filtered_slice.iter().collect()) + } + if !obs_lines_extracted.is_empty() { + // obsolete lines value was extracted + obs_lines = Some(obs_lines_extracted.iter().collect()); + } + } else { + // not a short option + // preserve it + v.push(arg.to_owned()); + } + } + println!("{:#?}",v); + (v, obs_lines) +} + pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(crate_version!()) @@ -357,6 +409,17 @@ impl fmt::Display for StrategyError { } impl Strategy { + /// Parse a strategy from obsolete line option value + fn from_obs(obs_value: &str) -> Result { + let n = parse_size(obs_value).map_err(StrategyError::Lines)?; + if n > 0 { + Ok(Self::Lines(n)) + } else { + Err(StrategyError::Lines(ParseSizeError::ParseFailure( + obs_value.to_string(), + ))) + } + } /// Parse a strategy from the command-line arguments. fn from(matches: &ArgMatches) -> Result { fn get_and_parse( @@ -506,7 +569,7 @@ impl fmt::Display for SettingsError { impl Settings { /// Parse a strategy from the command-line arguments. - fn from(matches: &ArgMatches) -> Result { + fn from(matches: &ArgMatches, obs_lines: &Option) -> Result { let additional_suffix = matches .get_one::(OPT_ADDITIONAL_SUFFIX) .unwrap() @@ -514,7 +577,19 @@ impl Settings { if additional_suffix.contains('/') { return Err(SettingsError::SuffixContainsSeparator(additional_suffix)); } - let strategy = Strategy::from(matches).map_err(SettingsError::Strategy)?; + + // obsolete lines option cannot be used simultaneously with OPT_LINES + let strategy = match obs_lines { + Some(obs_value) => { + if matches.value_source(OPT_LINES) == Some(ValueSource::CommandLine) { + return Err(SettingsError::Strategy(StrategyError::MultipleWays)); + } else { + Strategy::from_obs(obs_value).map_err(SettingsError::Strategy)? + } + } + None => Strategy::from(matches).map_err(SettingsError::Strategy)?, + }; + let (suffix_type, suffix_start) = suffix_type_from(matches)?; let suffix_length_str = matches.get_one::(OPT_SUFFIX_LENGTH).unwrap(); let suffix_length: usize = suffix_length_str diff --git a/tests/by-util/test_split.rs b/tests/by-util/test_split.rs index c5f32482ed6..fe753f0849f 100644 --- a/tests/by-util/test_split.rs +++ b/tests/by-util/test_split.rs @@ -2,7 +2,7 @@ // // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// spell-checker:ignore xzaaa sixhundredfiftyonebytes ninetyonebytes threebytes asciilowercase fghij klmno pqrst uvwxyz fivelines twohundredfortyonebytes onehundredlines nbbbb +// spell-checker:ignore xzaaa sixhundredfiftyonebytes ninetyonebytes threebytes asciilowercase fghij klmno pqrst uvwxyz fivelines twohundredfortyonebytes onehundredlines nbbbb dxen use crate::common::util::{AtPath, TestScenario}; use rand::{thread_rng, Rng, SeedableRng}; @@ -320,6 +320,156 @@ fn test_split_lines_number() { .stderr_only("split: invalid number of lines: '2fb'\n"); } +/// Test for obsolete lines option standalone +#[test] +fn test_split_obs_lines_standalone() { + let (at, mut ucmd) = at_and_ucmd!(); + let name = "obs-lines-standalone"; + RandomFile::new(&at, name).add_lines(4); + ucmd.args(&["-2", name]).succeeds().no_stderr().no_stdout(); + let glob = Glob::new(&at, ".", r"x[[:alpha:]][[:alpha:]]$"); + assert_eq!(glob.count(), 2); + assert_eq!(glob.collate(), at.read_bytes(name)); +} + +/// Test for invalid obsolete lines option +#[test] +fn test_split_invalid_obs_lines_standalone() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + at.touch("file"); + + scene + .ucmd() + .args(&["-2fb", "file"]) + .fails() + .code_is(1) + .stderr_only("error: unexpected argument '-f' found\n"); +} + +/// Test for obsolete lines option as part of combined short options +#[test] +fn test_split_obs_lines_within_combined_shorts() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let name = "obs-lines-within-shorts"; + RandomFile::new(&at, name).add_lines(400); + + scene + .ucmd() + .args(&["-d200xe", name]) + .succeeds() + .no_stderr() + .no_stdout(); + let glob = Glob::new(&at, ".", r"x[[:alpha:]][[:alpha:]]$"); + assert_eq!(glob.count(), 2); + assert_eq!(glob.collate(), at.read_bytes(name)) +} + +/// Test for obsolete lines option starts as part of combined short options +#[test] +fn test_split_obs_lines_starts_combined_shorts() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let name = "obs-lines-starts-shorts"; + RandomFile::new(&at, name).add_lines(400); + + scene + .ucmd() + .args(&["-x200d", name]) + .succeeds() + .no_stderr() + .no_stdout(); + let glob = Glob::new(&at, ".", r"x[[:alpha:]][[:alpha:]]$"); + assert_eq!(glob.count(), 2); + assert_eq!(glob.collate(), at.read_bytes(name)) +} + +/// Test for using both obsolete lines (standalone) option and short/long lines option simultaneously +#[test] +fn test_split_both_lines_and_obs_lines_standalone() { + // This test will ensure that if both lines option '-l' or '--lines' + // and obsolete lines option '-100' are used + // it fails + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + at.touch("file"); + + scene + .ucmd() + .args(&["-l", "2", "-2", "file"]) + .fails() + .code_is(1) + .stderr_contains("split: cannot split in more than one way\n"); + scene + .ucmd() + .args(&["--lines", "2", "-2", "file"]) + .fails() + .code_is(1) + .stderr_contains("split: cannot split in more than one way\n"); +} + +/// Test for using more than one obsolete lines option (standalone) +/// last one wins +#[test] +fn test_split_multiple_obs_lines_standalone() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let name = "multiple-obs-lines"; + RandomFile::new(&at, name).add_lines(400); + + scene + .ucmd() + .args(&["-3000", "-200", name]) + .succeeds() + .no_stderr() + .no_stdout(); + let glob = Glob::new(&at, ".", r"x[[:alpha:]][[:alpha:]]$"); + assert_eq!(glob.count(), 2); + assert_eq!(glob.collate(), at.read_bytes(name)) +} + +/// Test for using more than one obsolete lines option within combined shorts +/// last one wins +#[test] +fn test_split_multiple_obs_lines_within_combined() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let name = "multiple-obs-lines"; + RandomFile::new(&at, name).add_lines(400); + + scene + .ucmd() + .args(&["-x5000d -e200x", name]) + .succeeds() + .no_stderr() + .no_stdout(); + let glob = Glob::new(&at, ".", r"x[[:alpha:]][[:alpha:]]$"); + assert_eq!(glob.count(), 2); + assert_eq!(glob.collate(), at.read_bytes(name)) +} + +/// Test for using both obsolete lines option within combined shorts with conflicting -n option simultaneously +#[test] +fn test_split_obs_lines_within_combined_with_number() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + at.touch("file"); + + scene + .ucmd() + .args(&["-3dxen", "4", "file"]) + .fails() + .code_is(1) + .stderr_contains("split: cannot split in more than one way\n"); + scene + .ucmd() + .args(&["-dxe30n", "4", "file"]) + .fails() + .code_is(1) + .stderr_contains("split: cannot split in more than one way\n"); +} + #[test] fn test_split_invalid_bytes_size() { new_ucmd!() From 70dd8eb8dcdfffa7b69a22bc9a3162408d912f84 Mon Sep 17 00:00:00 2001 From: zhitkoff Date: Sat, 26 Aug 2023 13:22:36 -0400 Subject: [PATCH 0015/2851] split: updates to target correct GNU coreutils release --- util/build-gnu.sh | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/util/build-gnu.sh b/util/build-gnu.sh index 587cf858096..f6be73ff470 100755 --- a/util/build-gnu.sh +++ b/util/build-gnu.sh @@ -32,10 +32,16 @@ fi ### +release_tag_GNU="v9.3" + if test ! -d "${path_GNU}"; then echo "Could not find GNU coreutils (expected at '${path_GNU}')" echo "Run the following to download into the expected path:" echo "git clone --recurse-submodules https://github.com/coreutils/coreutils.git \"${path_GNU}\"" + echo "After downloading GNU coreutils to \"${path_GNU}\" run the following commands to cheout latest release tag" + echo "cd \"${path_GNU}\"" + echo "git fetch --all --tags" + echo "git checkout tags/\"${release_tag_GNU}\"" exit 1 fi @@ -152,17 +158,8 @@ sed -i -e '/tests\/misc\/invalid-opt.pl/ D' \ sed -i -e '/tests\/misc\/seq-precision.sh/ D' \ Makefile -# TODO: many files and some directories modified with 'sed' in the sections below either no longer exist or have been moved -# TODO: Might need to review and updated sections below -# TODO: As a result this script will fail when executed normally as 'bash util/build-gnu.hs' due to the 'set -e' set at the beginning -# TODO: The rest of the 'sed' commands after first failure in this scenario will not be executed as bash will exit on first error -# TODO: However, the behaviour might be different when running it via GitHub actions (GnuTests) -# TODO: For now, when running in local a workaround would be to comment out the 'set -e' at the beginning of the file - # printf doesn't limit the values used in its arg, so this produced ~2GB of output -# TODO: this is the first one to likely fail as tests/misc/printf.sh does not exist/have been moved sed -i '/INT_OFLOW/ D' tests/misc/printf.sh -# TODO: all commands below might not be executed # Use the system coreutils where the test fails due to error in a util that is not the one being tested sed -i 's|stat|/usr/bin/stat|' tests/touch/60-seconds.sh tests/misc/sort-compress-proc.sh From eac08f72c2ef97a7d7206fb3f98fc28a447172be Mon Sep 17 00:00:00 2001 From: zhitkoff Date: Sat, 26 Aug 2023 13:24:08 -0400 Subject: [PATCH 0016/2851] split: double quotes --- util/build-gnu.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util/build-gnu.sh b/util/build-gnu.sh index f6be73ff470..74ec2967066 100755 --- a/util/build-gnu.sh +++ b/util/build-gnu.sh @@ -41,7 +41,7 @@ if test ! -d "${path_GNU}"; then echo "After downloading GNU coreutils to \"${path_GNU}\" run the following commands to cheout latest release tag" echo "cd \"${path_GNU}\"" echo "git fetch --all --tags" - echo "git checkout tags/\"${release_tag_GNU}\"" + echo "git checkout tags/${release_tag_GNU}" exit 1 fi From 4a4759c43c7cd10c81e53f9e352cd9f8f2a5fb63 Mon Sep 17 00:00:00 2001 From: zhitkoff Date: Sat, 26 Aug 2023 13:44:34 -0400 Subject: [PATCH 0017/2851] split: updated to SYSTEM_TIMEOUT in a few more places --- util/build-gnu.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/util/build-gnu.sh b/util/build-gnu.sh index 74ec2967066..4e196debe5c 100755 --- a/util/build-gnu.sh +++ b/util/build-gnu.sh @@ -173,10 +173,10 @@ sed -i 's|touch |/usr/bin/touch |' tests/cp/reflink-perm.sh tests/ls/block-size. sed -i 's|ln -|/usr/bin/ln -|' tests/cp/link-deref.sh sed -i 's|cp |/usr/bin/cp |' tests/mv/hard-2.sh sed -i 's|paste |/usr/bin/paste |' tests/misc/od-endian.sh -sed -i 's|timeout |/usr/bin/timeout |' tests/tail-2/follow-stdin.sh +sed -i 's|timeout |'"${SYSTEM_TIMEOUT}"' |' tests/tail-2/follow-stdin.sh # Add specific timeout to tests that currently hang to limit time spent waiting -sed -i 's|\(^\s*\)seq \$|\1/usr/bin/timeout 0.1 seq \$|' tests/misc/seq-precision.sh tests/misc/seq-long-double.sh +sed -i 's|\(^\s*\)seq \$|\1'"${SYSTEM_TIMEOUT}"' 0.1 seq \$|' tests/misc/seq-precision.sh tests/misc/seq-long-double.sh # Remove dup of /usr/bin/ when executed several times grep -rlE '/usr/bin/\s?/usr/bin' init.cfg tests/* | xargs --no-run-if-empty sed -Ei 's|/usr/bin/\s?/usr/bin/|/usr/bin/|g' From a384973b1a7e40435e165ab7a65a59ab26da7fd8 Mon Sep 17 00:00:00 2001 From: zhitkoff Date: Sat, 26 Aug 2023 13:51:21 -0400 Subject: [PATCH 0018/2851] split: remove dup for /usr/local/bin when executed several times --- util/build-gnu.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/util/build-gnu.sh b/util/build-gnu.sh index 4e196debe5c..ce15dd3f8e5 100755 --- a/util/build-gnu.sh +++ b/util/build-gnu.sh @@ -180,6 +180,7 @@ sed -i 's|\(^\s*\)seq \$|\1'"${SYSTEM_TIMEOUT}"' 0.1 seq \$|' tests/misc/seq-pre # Remove dup of /usr/bin/ when executed several times grep -rlE '/usr/bin/\s?/usr/bin' init.cfg tests/* | xargs --no-run-if-empty sed -Ei 's|/usr/bin/\s?/usr/bin/|/usr/bin/|g' +grep -rlE '/usr/local/bin/\s?/usr/local/bin' init.cfg tests/* | xargs --no-run-if-empty sed -Ei 's|/usr/local/bin/\s?/usr/local/bin/|/usr/local/bin/|g' #### Adjust tests to make them work with Rust/coreutils # in some cases, what we are doing in rust/coreutils is good (or better) From 0edba89b55ba51d974c90645079454f128c2ae6b Mon Sep 17 00:00:00 2001 From: zhitkoff Date: Sat, 26 Aug 2023 13:53:36 -0400 Subject: [PATCH 0019/2851] split: comments --- util/build-gnu.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util/build-gnu.sh b/util/build-gnu.sh index ce15dd3f8e5..57764ed5076 100755 --- a/util/build-gnu.sh +++ b/util/build-gnu.sh @@ -178,7 +178,7 @@ sed -i 's|timeout |'"${SYSTEM_TIMEOUT}"' |' tests/tail-2/follow-stdin.sh # Add specific timeout to tests that currently hang to limit time spent waiting sed -i 's|\(^\s*\)seq \$|\1'"${SYSTEM_TIMEOUT}"' 0.1 seq \$|' tests/misc/seq-precision.sh tests/misc/seq-long-double.sh -# Remove dup of /usr/bin/ when executed several times +# Remove dup of /usr/bin/ and /usr/local/bin/ when executed several times grep -rlE '/usr/bin/\s?/usr/bin' init.cfg tests/* | xargs --no-run-if-empty sed -Ei 's|/usr/bin/\s?/usr/bin/|/usr/bin/|g' grep -rlE '/usr/local/bin/\s?/usr/local/bin' init.cfg tests/* | xargs --no-run-if-empty sed -Ei 's|/usr/local/bin/\s?/usr/local/bin/|/usr/local/bin/|g' From 4623575a6641bba9c4b45303d28386f7e92cc2b1 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Mon, 28 Aug 2023 22:51:45 +0200 Subject: [PATCH 0020/2851] echo: fix wrapping behavior of octal sequences --- src/uu/echo/src/echo.rs | 109 ++++++++++++++++++------------------- tests/by-util/test_echo.rs | 17 ++++++ 2 files changed, 71 insertions(+), 55 deletions(-) diff --git a/src/uu/echo/src/echo.rs b/src/uu/echo/src/echo.rs index cd9467714aa..4aba703a17d 100644 --- a/src/uu/echo/src/echo.rs +++ b/src/uu/echo/src/echo.rs @@ -21,73 +21,72 @@ mod options { pub const DISABLE_BACKSLASH_ESCAPE: &str = "disable_backslash_escape"; } -fn parse_code( - input: &mut Peekable, - base: u32, - max_digits: u32, - bits_per_digit: u32, -) -> Option { - let mut ret = 0x8000_0000; - for _ in 0..max_digits { - match input.peek().and_then(|c| c.to_digit(base)) { - Some(n) => ret = (ret << bits_per_digit) | n, +/// Parse the numeric part of the `\xHHH` and `\0NNN` escape sequences +fn parse_code(input: &mut Peekable, base: u8, max_digits: u32) -> Option { + // All arithmetic on `ret` needs to be wrapping, because octal input can + // take 3 digits, which is 9 bits, and therefore more than what fits in a + // `u8`. GNU just seems to wrap these values. + // Note that if we instead make `ret` a `u32` and use `char::from_u32` will + // yield incorrect results because it will interpret values larger than + // `u8::MAX` as unicode. + let mut ret = input.peek().and_then(|c| c.to_digit(base as u32))? as u8; + + // We can safely ifgnore the None case because we just peeked it. + let _ = input.next(); + + for _ in 1..max_digits { + match input.peek().and_then(|c| c.to_digit(base as u32)) { + Some(n) => ret = ret.wrapping_mul(base).wrapping_add(n as u8), None => break, } - input.next(); + // We can safely ifgnore the None case because we just peeked it. + let _ = input.next(); } - std::char::from_u32(ret) + + Some(ret.into()) } fn print_escaped(input: &str, mut output: impl Write) -> io::Result { - let mut should_stop = false; - - let mut buffer = ['\\'; 2]; - - // TODO `cargo +nightly clippy` complains that `.peek()` is never - // called on `iter`. However, `peek()` is called inside the - // `parse_code()` function that borrows `iter`. let mut iter = input.chars().peekable(); - while let Some(mut c) = iter.next() { - let mut start = 1; - - if c == '\\' { - if let Some(next) = iter.next() { - c = match next { - '\\' => '\\', - 'a' => '\x07', - 'b' => '\x08', - 'c' => { - should_stop = true; - break; - } - 'e' => '\x1b', - 'f' => '\x0c', - 'n' => '\n', - 'r' => '\r', - 't' => '\t', - 'v' => '\x0b', - 'x' => parse_code(&mut iter, 16, 2, 4).unwrap_or_else(|| { - start = 0; - next - }), - '0' => parse_code(&mut iter, 8, 3, 3).unwrap_or('\0'), - _ => { - start = 0; - next - } - }; - } + while let Some(c) = iter.next() { + if c != '\\' { + write!(output, "{c}")?; + continue; } - buffer[1] = c; - - // because printing char slices is apparently not available in the standard library - for ch in &buffer[start..] { - write!(output, "{ch}")?; + if let Some(next) = iter.next() { + let unescaped = match next { + '\\' => '\\', + 'a' => '\x07', + 'b' => '\x08', + 'c' => return Ok(true), + 'e' => '\x1b', + 'f' => '\x0c', + 'n' => '\n', + 'r' => '\r', + 't' => '\t', + 'v' => '\x0b', + 'x' => { + if let Some(c) = parse_code(&mut iter, 16, 2) { + c + } else { + write!(output, "\\")?; + 'x' + } + } + '0' => parse_code(&mut iter, 8, 3).unwrap_or('\0'), + c => { + write!(output, "\\")?; + c + } + }; + write!(output, "{unescaped}")?; + } else { + write!(output, "\\")?; } } - Ok(should_stop) + Ok(false) } #[uucore::main] diff --git a/tests/by-util/test_echo.rs b/tests/by-util/test_echo.rs index 3a8e7f86b34..82137c715ee 100644 --- a/tests/by-util/test_echo.rs +++ b/tests/by-util/test_echo.rs @@ -236,3 +236,20 @@ fn test_hyphen_values_between() { .success() .stdout_is("dumdum dum dum dum -e dum\n"); } + +#[test] +fn wrapping_octal() { + // Some odd behavior of GNU. Values of \0400 and greater do not fit in the + // u8 that we write to stdout. So we test that it wraps: + // + // We give it this input: + // \o501 = 1_0100_0001 (yes, **9** bits) + // This should be wrapped into: + // \o101 = 'A' = 0100_0001, + // because we only write a single character + new_ucmd!() + .arg("-e") + .arg("\\0501") + .succeeds() + .stdout_is("A\n"); +} From 2f35989ac34cda92b65328d325bdc615057e2dbe Mon Sep 17 00:00:00 2001 From: zhitkoff Date: Mon, 28 Aug 2023 18:26:48 -0400 Subject: [PATCH 0021/2851] split: comments --- src/uu/split/src/split.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/uu/split/src/split.rs b/src/uu/split/src/split.rs index 77c87e7f5c5..698c7818c03 100644 --- a/src/uu/split/src/split.rs +++ b/src/uu/split/src/split.rs @@ -108,7 +108,6 @@ fn handle_obsolete(args: &[String]) -> (Vec, Option) { v.push(arg.to_owned()); } } - // println!("{:#?} , {:#?}", v, obs_lines); (v, obs_lines) } @@ -465,8 +464,7 @@ impl Strategy { // Check that the user is not specifying more than one strategy. // // Note: right now, this exact behavior cannot be handled by - // `ArgGroup` since `ArgGroup` considers a default value `Arg` - // as "defined". + // overrides_with_all() due to obsolete lines value option match ( obs_lines, matches.value_source(OPT_LINES) == Some(ValueSource::CommandLine), From e79753c1cf25db25104e853e52e7f687dd776cfb Mon Sep 17 00:00:00 2001 From: zhitkoff Date: Tue, 29 Aug 2023 13:58:26 -0400 Subject: [PATCH 0022/2851] split: refactor handle_obsolete() function --- src/uu/split/src/split.rs | 71 +++++++++++++++++++++------------------ 1 file changed, 39 insertions(+), 32 deletions(-) diff --git a/src/uu/split/src/split.rs b/src/uu/split/src/split.rs index 698c7818c03..876d04606f0 100644 --- a/src/uu/split/src/split.rs +++ b/src/uu/split/src/split.rs @@ -72,43 +72,50 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { /// `split -x300e -22 file` would mean `split -x -e -l 22 file` (last obsolete lines option wins) /// following GNU `split` behavior fn handle_obsolete(args: &[String]) -> (Vec, Option) { - let mut v: Vec = vec![]; let mut obs_lines = None; - for arg in args.iter() { - let slice = &arg; - if slice.starts_with('-') && !slice.starts_with("--") { - // start of the short option string - // extract numeric part and filter it out - let mut obs_lines_extracted: Vec = vec![]; - let filtered_slice: Vec = slice - .chars() - .filter(|c| { - if c.is_ascii_digit() { - obs_lines_extracted.push(*c); - false + let filtered_args = args + .iter() + .filter_map(|slice| { + if slice.starts_with('-') && !slice.starts_with("--") { + // start of the short option string + // extract numeric part and filter it out + let mut obs_lines_extracted: Vec = vec![]; + let filtered_slice: Vec = slice + .chars() + .filter(|c| { + if c.is_ascii_digit() { + obs_lines_extracted.push(*c); + false + } else { + true + } + }) + .collect(); + + if obs_lines_extracted.is_empty() { + // no obsolete lines value found/extracted + Some(slice.to_owned()) + } else { + // obsolete lines value was extracted + obs_lines = Some(obs_lines_extracted.iter().collect()); + if filtered_slice.get(1).is_some() { + // there were some short options in front of or after obsolete lines value + // i.e. '-xd100' or '-100de' or similar, which after extraction of obsolete lines value + // would look like '-xd' or '-de' or similar + // preserve it + Some(filtered_slice.iter().collect()) } else { - true + None } - }) - .collect(); - - if filtered_slice.get(1).is_some() { - // there were some short options in front of obsolete lines number - // i.e. '-xd100' or similar + } + } else { + // not a short option // preserve it - v.push(filtered_slice.iter().collect()); - } - if !obs_lines_extracted.is_empty() { - // obsolete lines value was extracted - obs_lines = Some(obs_lines_extracted.iter().collect()); + Some(slice.to_owned()) } - } else { - // not a short option - // preserve it - v.push(arg.to_owned()); - } - } - (v, obs_lines) + }) + .collect(); + (filtered_args, obs_lines) } pub fn uu_app() -> Command { From 15c7170d20c532889ac8f3470c5d59bc12d10f2a Mon Sep 17 00:00:00 2001 From: zhitkoff Date: Tue, 29 Aug 2023 15:49:47 -0400 Subject: [PATCH 0023/2851] split: fix for GNU Tests regression + tests --- src/uu/split/src/split.rs | 15 ++++++--- tests/by-util/test_split.rs | 65 +++++++++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+), 4 deletions(-) diff --git a/src/uu/split/src/split.rs b/src/uu/split/src/split.rs index 876d04606f0..dae24d36b7c 100644 --- a/src/uu/split/src/split.rs +++ b/src/uu/split/src/split.rs @@ -76,8 +76,16 @@ fn handle_obsolete(args: &[String]) -> (Vec, Option) { let filtered_args = args .iter() .filter_map(|slice| { - if slice.starts_with('-') && !slice.starts_with("--") { + if slice.starts_with('-') + && !slice.starts_with("--") + && !slice.starts_with("-a") + && !slice.starts_with("-b") + && !slice.starts_with("-C") + && !slice.starts_with("-l") + && !slice.starts_with("-n") + { // start of the short option string + // that can have obsolete lines option value in it // extract numeric part and filter it out let mut obs_lines_extracted: Vec = vec![]; let filtered_slice: Vec = slice @@ -102,15 +110,14 @@ fn handle_obsolete(args: &[String]) -> (Vec, Option) { // there were some short options in front of or after obsolete lines value // i.e. '-xd100' or '-100de' or similar, which after extraction of obsolete lines value // would look like '-xd' or '-de' or similar - // preserve it Some(filtered_slice.iter().collect()) } else { None } } } else { - // not a short option - // preserve it + // either not a short option + // or a short option that cannot have obsolete lines value in it Some(slice.to_owned()) } }) diff --git a/tests/by-util/test_split.rs b/tests/by-util/test_split.rs index aabfcbe90da..9fba2177e40 100644 --- a/tests/by-util/test_split.rs +++ b/tests/by-util/test_split.rs @@ -170,6 +170,22 @@ fn test_split_str_prefixed_chunks_by_bytes() { assert_eq!(glob.collate(), at.read_bytes(name)); } +// Test short bytes option concatenated with value +#[test] +fn test_split_by_bytes_short_concatenated_with_value() { + let (at, mut ucmd) = at_and_ucmd!(); + let name = "split_by_bytes_short_concatenated_with_value"; + RandomFile::new(&at, name).add_bytes(10000); + ucmd.args(&["-b1000", name]).succeeds(); + + let glob = Glob::new(&at, ".", r"x[[:alpha:]][[:alpha:]]$"); + assert_eq!(glob.count(), 10); + for filename in glob.collect() { + assert_eq!(glob.directory.metadata(&filename).len(), 1000); + } + assert_eq!(glob.collate(), at.read_bytes(name)); +} + // This is designed to test what happens when the desired part size is not a // multiple of the buffer size and we hopefully don't overshoot the desired part // size. @@ -326,6 +342,19 @@ fn test_split_lines_number() { .stderr_only("split: invalid number of lines: 'file'\n"); } +// Test short lines option with value concatenated +#[test] +fn test_split_lines_short_concatenated_with_value() { + let (at, mut ucmd) = at_and_ucmd!(); + let name = "split_num_prefixed_chunks_by_lines"; + RandomFile::new(&at, name).add_lines(10000); + ucmd.args(&["-l1000", name]).succeeds(); + + let glob = Glob::new(&at, ".", r"x[[:alpha:]][[:alpha:]]$"); + assert_eq!(glob.count(), 10); + assert_eq!(glob.collate(), at.read_bytes(name)); +} + /// Test for obsolete lines option standalone #[test] fn test_split_obs_lines_standalone() { @@ -692,6 +721,19 @@ fn test_invalid_suffix_length() { .stderr_contains("invalid suffix length: 'xyz'"); } +// Test short suffix length option with value concatenated +#[test] +fn test_split_suffix_length_short_concatenated_with_value() { + let (at, mut ucmd) = at_and_ucmd!(); + let name = "split_num_prefixed_chunks_by_lines"; + RandomFile::new(&at, name).add_lines(10000); + ucmd.args(&["-a4", name]).succeeds(); + + let glob = Glob::new(&at, ".", r"x[[:alpha:]][[:alpha:]][[:alpha:]][[:alpha:]]$"); + assert_eq!(glob.count(), 10); + assert_eq!(glob.collate(), at.read_bytes(name)); +} + #[test] fn test_include_newlines() { let (at, mut ucmd) = at_and_ucmd!(); @@ -710,6 +752,19 @@ fn test_include_newlines() { assert_eq!(s, "5\n"); } +// Test short number of chunks option concatenated with value +#[test] +fn test_split_number_chunks_short_concatenated_with_value() { + let (at, mut ucmd) = at_and_ucmd!(); + ucmd.args(&["-n3", "threebytes.txt"]) + .succeeds() + .no_stdout() + .no_stderr(); + assert_eq!(at.read("xaa"), "a"); + assert_eq!(at.read("xab"), "b"); + assert_eq!(at.read("xac"), "c"); +} + #[test] fn test_allow_empty_files() { let (at, mut ucmd) = at_and_ucmd!(); @@ -784,6 +839,16 @@ fn test_line_bytes() { assert_eq!(at.read("xad"), "ee\n"); } +#[test] +fn test_line_bytes_concatenated_with_value() { + let (at, mut ucmd) = at_and_ucmd!(); + ucmd.args(&["-C8", "letters.txt"]).succeeds(); + assert_eq!(at.read("xaa"), "aaaaaaaa"); + assert_eq!(at.read("xab"), "a\nbbbb\n"); + assert_eq!(at.read("xac"), "cccc\ndd\n"); + assert_eq!(at.read("xad"), "ee\n"); +} + #[test] fn test_line_bytes_no_final_newline() { let (at, mut ucmd) = at_and_ucmd!(); From 1ad10dd371cfe341993c23d302c7a002a85953ae Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Tue, 29 Aug 2023 21:54:19 +0200 Subject: [PATCH 0024/2851] echo: add support for \NNN octal escape sequence --- src/uu/echo/src/echo.rs | 10 ++++++++++ tests/by-util/test_echo.rs | 27 +++++++++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/src/uu/echo/src/echo.rs b/src/uu/echo/src/echo.rs index 4aba703a17d..565166842db 100644 --- a/src/uu/echo/src/echo.rs +++ b/src/uu/echo/src/echo.rs @@ -54,6 +54,16 @@ fn print_escaped(input: &str, mut output: impl Write) -> io::Result { continue; } + // This is for the \NNN syntax for octal sequences. + // Note that '0' is intentionally omitted because that + // would be the \0NNN syntax. + if let Some('1'..='8') = iter.peek() { + if let Some(parsed) = parse_code(&mut iter, 8, 3) { + write!(output, "{parsed}")?; + continue; + } + } + if let Some(next) = iter.next() { let unescaped = match next { '\\' => '\\', diff --git a/tests/by-util/test_echo.rs b/tests/by-util/test_echo.rs index 82137c715ee..7de963973a2 100644 --- a/tests/by-util/test_echo.rs +++ b/tests/by-util/test_echo.rs @@ -253,3 +253,30 @@ fn wrapping_octal() { .succeeds() .stdout_is("A\n"); } + +#[test] +fn old_octal_syntax() { + new_ucmd!() + .arg("-e") + .arg("\\1foo") + .succeeds() + .stdout_is("\x01foo\n"); + + new_ucmd!() + .arg("-e") + .arg("\\43foo") + .succeeds() + .stdout_is("#foo\n"); + + new_ucmd!() + .arg("-e") + .arg("\\101foo") + .succeeds() + .stdout_is("Afoo\n"); + + new_ucmd!() + .arg("-e") + .arg("\\1011") + .succeeds() + .stdout_is("A1\n"); +} From 7f905a3b8d6d08c271d71b25dcba3d9559f83959 Mon Sep 17 00:00:00 2001 From: zhitkoff Date: Tue, 29 Aug 2023 16:35:00 -0400 Subject: [PATCH 0025/2851] split: edge case for obs lines within combined shorts + test --- src/uu/split/src/split.rs | 9 ++++++++- tests/by-util/test_split.rs | 21 +++++++++++++++++---- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/src/uu/split/src/split.rs b/src/uu/split/src/split.rs index dae24d36b7c..afe635456bc 100644 --- a/src/uu/split/src/split.rs +++ b/src/uu/split/src/split.rs @@ -88,13 +88,20 @@ fn handle_obsolete(args: &[String]) -> (Vec, Option) { // that can have obsolete lines option value in it // extract numeric part and filter it out let mut obs_lines_extracted: Vec = vec![]; + let mut obs_lines_end_reached = false; let filtered_slice: Vec = slice .chars() .filter(|c| { - if c.is_ascii_digit() { + // To correctly process scenario like '-x200a4' + // we need to stop extracting digits once alphabetic character is encountered + // after we already have something in obs_lines_extracted + if c.is_ascii_digit() && !obs_lines_end_reached { obs_lines_extracted.push(*c); false } else { + if !obs_lines_extracted.is_empty() { + obs_lines_end_reached = true; + } true } }) diff --git a/tests/by-util/test_split.rs b/tests/by-util/test_split.rs index 9fba2177e40..3280fff675d 100644 --- a/tests/by-util/test_split.rs +++ b/tests/by-util/test_split.rs @@ -170,7 +170,7 @@ fn test_split_str_prefixed_chunks_by_bytes() { assert_eq!(glob.collate(), at.read_bytes(name)); } -// Test short bytes option concatenated with value +/// Test short bytes option concatenated with value #[test] fn test_split_by_bytes_short_concatenated_with_value() { let (at, mut ucmd) = at_and_ucmd!(); @@ -342,7 +342,7 @@ fn test_split_lines_number() { .stderr_only("split: invalid number of lines: 'file'\n"); } -// Test short lines option with value concatenated +/// Test short lines option with value concatenated #[test] fn test_split_lines_short_concatenated_with_value() { let (at, mut ucmd) = at_and_ucmd!(); @@ -401,6 +401,19 @@ fn test_split_obs_lines_within_combined_shorts() { assert_eq!(glob.collate(), at.read_bytes(name)) } +/// Test for obsolete lines option as part of combined short options with tailing suffix length with value +#[test] +fn test_split_obs_lines_within_combined_shorts_tailing_suffix_length() { + let (at, mut ucmd) = at_and_ucmd!(); + let name = "obs-lines-combined-shorts-tailing-suffix-length"; + RandomFile::new(&at, name).add_lines(1000); + ucmd.args(&["-d200a4", name]).succeeds(); + + let glob = Glob::new(&at, ".", r"x\d\d\d\d$"); + assert_eq!(glob.count(), 5); + assert_eq!(glob.collate(), at.read_bytes(name)); +} + /// Test for obsolete lines option starts as part of combined short options #[test] fn test_split_obs_lines_starts_combined_shorts() { @@ -721,7 +734,7 @@ fn test_invalid_suffix_length() { .stderr_contains("invalid suffix length: 'xyz'"); } -// Test short suffix length option with value concatenated +/// Test short suffix length option with value concatenated #[test] fn test_split_suffix_length_short_concatenated_with_value() { let (at, mut ucmd) = at_and_ucmd!(); @@ -752,7 +765,7 @@ fn test_include_newlines() { assert_eq!(s, "5\n"); } -// Test short number of chunks option concatenated with value +/// Test short number of chunks option concatenated with value #[test] fn test_split_number_chunks_short_concatenated_with_value() { let (at, mut ucmd) = at_and_ucmd!(); From b2ebe6a1d180fca68b5891ecf542f956e7df8331 Mon Sep 17 00:00:00 2001 From: zhitkoff Date: Wed, 30 Aug 2023 11:15:11 -0400 Subject: [PATCH 0026/2851] build-gnu.sh: target GNU release v9.4 --- util/build-gnu.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util/build-gnu.sh b/util/build-gnu.sh index a6d8de29ef6..21a231bc01d 100755 --- a/util/build-gnu.sh +++ b/util/build-gnu.sh @@ -32,7 +32,7 @@ fi ### -release_tag_GNU="v9.3" +release_tag_GNU="v9.4" if test ! -d "${path_GNU}"; then echo "Could not find GNU coreutils (expected at '${path_GNU}')" From 6f37b4b4cfcbe45725d91d63dadabd1aa9873a2b Mon Sep 17 00:00:00 2001 From: zhitkoff Date: Wed, 30 Aug 2023 19:29:57 -0400 Subject: [PATCH 0027/2851] split: hyphenated values + tests --- src/uu/split/src/split.rs | 50 ++++++++++++++++++++--- tests/by-util/test_split.rs | 81 ++++++++++++++++++++++++++++++++++--- 2 files changed, 120 insertions(+), 11 deletions(-) diff --git a/src/uu/split/src/split.rs b/src/uu/split/src/split.rs index afe635456bc..7834dfe6821 100644 --- a/src/uu/split/src/split.rs +++ b/src/uu/split/src/split.rs @@ -73,11 +73,18 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { /// following GNU `split` behavior fn handle_obsolete(args: &[String]) -> (Vec, Option) { let mut obs_lines = None; + let mut preceding_long_opt_req_value = false; + let mut preceding_short_opt_req_value = false; let filtered_args = args .iter() .filter_map(|slice| { + let filter: Option; + // check if the slice is a true short option (and not hyphen prefixed value of an option) + // and if so, a short option that can contain obsolete lines value if slice.starts_with('-') && !slice.starts_with("--") + && !preceding_long_opt_req_value + && !preceding_short_opt_req_value && !slice.starts_with("-a") && !slice.starts_with("-b") && !slice.starts_with("-C") @@ -109,7 +116,7 @@ fn handle_obsolete(args: &[String]) -> (Vec, Option) { if obs_lines_extracted.is_empty() { // no obsolete lines value found/extracted - Some(slice.to_owned()) + filter = Some(slice.to_owned()); } else { // obsolete lines value was extracted obs_lines = Some(obs_lines_extracted.iter().collect()); @@ -117,16 +124,41 @@ fn handle_obsolete(args: &[String]) -> (Vec, Option) { // there were some short options in front of or after obsolete lines value // i.e. '-xd100' or '-100de' or similar, which after extraction of obsolete lines value // would look like '-xd' or '-de' or similar - Some(filtered_slice.iter().collect()) + filter = Some(filtered_slice.iter().collect()); } else { - None + filter = None; } } } else { // either not a short option // or a short option that cannot have obsolete lines value in it - Some(slice.to_owned()) + filter = Some(slice.to_owned()); } + // capture if current slice is a preceding long option that requires value and does not use '=' to assign that value + // following slice should be treaded as value for this option + // even if it starts with '-' (which would be treated as hyphen prefixed value) + if slice.starts_with("--") { + preceding_long_opt_req_value = &slice[2..] == OPT_BYTES + || &slice[2..] == OPT_LINE_BYTES + || &slice[2..] == OPT_LINES + || &slice[2..] == OPT_ADDITIONAL_SUFFIX + || &slice[2..] == OPT_FILTER + || &slice[2..] == OPT_NUMBER + || &slice[2..] == OPT_SUFFIX_LENGTH; + } + // capture if current slice is a preceding short option that requires value and does not have value in the same slice (value separated by whitespace) + // following slice should be treaded as value for this option + // even if it starts with '-' (which would be treated as hyphen prefixed value) + preceding_short_opt_req_value = + slice == "-b" || slice == "-C" || slice == "-l" || slice == "-n" || slice == "-a"; + // slice is a value + // reset preceding option flags + if !slice.starts_with('-') { + preceding_short_opt_req_value = false; + preceding_long_opt_req_value = false; + } + // return filter + filter }) .collect(); (filtered_args, obs_lines) @@ -144,6 +176,7 @@ pub fn uu_app() -> Command { Arg::new(OPT_BYTES) .short('b') .long(OPT_BYTES) + .allow_hyphen_values(true) .value_name("SIZE") .help("put SIZE bytes per output file"), ) @@ -151,14 +184,15 @@ pub fn uu_app() -> Command { Arg::new(OPT_LINE_BYTES) .short('C') .long(OPT_LINE_BYTES) + .allow_hyphen_values(true) .value_name("SIZE") - .default_value("2") .help("put at most SIZE bytes of lines per output file"), ) .arg( Arg::new(OPT_LINES) .short('l') .long(OPT_LINES) + .allow_hyphen_values(true) .value_name("NUMBER") .default_value("1000") .help("put NUMBER lines/records per output file"), @@ -167,6 +201,7 @@ pub fn uu_app() -> Command { Arg::new(OPT_NUMBER) .short('n') .long(OPT_NUMBER) + .allow_hyphen_values(true) .value_name("CHUNKS") .help("generate CHUNKS output files; see explanation below"), ) @@ -174,6 +209,7 @@ pub fn uu_app() -> Command { .arg( Arg::new(OPT_ADDITIONAL_SUFFIX) .long(OPT_ADDITIONAL_SUFFIX) + .allow_hyphen_values(true) .value_name("SUFFIX") .default_value("") .help("additional SUFFIX to append to output file names"), @@ -181,6 +217,7 @@ pub fn uu_app() -> Command { .arg( Arg::new(OPT_FILTER) .long(OPT_FILTER) + .allow_hyphen_values(true) .value_name("COMMAND") .value_hint(clap::ValueHint::CommandName) .help( @@ -250,9 +287,10 @@ pub fn uu_app() -> Command { Arg::new(OPT_SUFFIX_LENGTH) .short('a') .long(OPT_SUFFIX_LENGTH) + .allow_hyphen_values(true) .value_name("N") .default_value(OPT_DEFAULT_SUFFIX_LENGTH) - .help("use suffixes of fixed length N. 0 implies dynamic length."), + .help("use suffixes of fixed length N. 0 implies dynamic length, starting with 2"), ) .arg( Arg::new(OPT_VERBOSE) diff --git a/tests/by-util/test_split.rs b/tests/by-util/test_split.rs index 3280fff675d..466dabda9ab 100644 --- a/tests/by-util/test_split.rs +++ b/tests/by-util/test_split.rs @@ -254,6 +254,18 @@ fn test_additional_suffix_no_slash() { .usage_error("invalid suffix 'a/b', contains directory separator"); } +#[test] +fn test_split_additional_suffix_hyphen_value() { + let (at, mut ucmd) = at_and_ucmd!(); + let name = "split_additional_suffix"; + RandomFile::new(&at, name).add_lines(2000); + ucmd.args(&["--additional-suffix", "-300", name]).succeeds(); + + let glob = Glob::new(&at, ".", r"x[[:alpha:]][[:alpha:]]-300$"); + assert_eq!(glob.count(), 2); + assert_eq!(glob.collate(), at.read_bytes(name)); +} + // note: the test_filter* tests below are unix-only // windows support has been waived for now because of the difficulty of getting // the `cmd` call right @@ -436,9 +448,9 @@ fn test_split_obs_lines_starts_combined_shorts() { /// Test for using both obsolete lines (standalone) option and short/long lines option simultaneously #[test] fn test_split_both_lines_and_obs_lines_standalone() { - // This test will ensure that if both lines option '-l' or '--lines' - // and obsolete lines option '-100' are used - // it fails + // This test will ensure that: + // if both lines option '-l' or '--lines' (with value) and obsolete lines option '-100' are used - it fails + // if standalone lines option is used incorrectly and treated as a hyphen prefixed value of other option - it fails let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; at.touch("file"); @@ -455,18 +467,77 @@ fn test_split_both_lines_and_obs_lines_standalone() { .fails() .code_is(1) .stderr_contains("split: cannot split in more than one way\n"); +} + +/// Test for using obsolete lines option incorrectly, so it is treated as a hyphen prefixed value of other option +#[test] +fn test_split_obs_lines_as_other_option_value() { + // This test will ensure that: + // if obsolete lines option is used incorrectly and treated as a hyphen prefixed value of other option - it fails + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + at.touch("file"); + scene .ucmd() .args(&["--lines", "-200", "file"]) .fails() .code_is(1) - .stderr_contains("split: cannot split in more than one way\n"); + .stderr_contains("split: invalid number of lines: '-200'\n"); scene .ucmd() .args(&["-l", "-200", "file"]) .fails() .code_is(1) - .stderr_contains("split: cannot split in more than one way\n"); + .stderr_contains("split: invalid number of lines: '-200'\n"); + scene + .ucmd() + .args(&["-a", "-200", "file"]) + .fails() + .code_is(1) + .stderr_contains("split: invalid suffix length: '-200'\n"); + scene + .ucmd() + .args(&["--suffix-length", "-d200e", "file"]) + .fails() + .code_is(1) + .stderr_contains("split: invalid suffix length: '-d200e'\n"); + scene + .ucmd() + .args(&["-C", "-200", "file"]) + .fails() + .code_is(1) + .stderr_contains("split: invalid number of bytes: '-200'\n"); + scene + .ucmd() + .args(&["--line-bytes", "-x200a4", "file"]) + .fails() + .code_is(1) + .stderr_contains("split: invalid number of bytes: '-x200a4'\n"); + scene + .ucmd() + .args(&["-b", "-200", "file"]) + .fails() + .code_is(1) + .stderr_contains("split: invalid number of bytes: '-200'\n"); + scene + .ucmd() + .args(&["--bytes", "-200xd", "file"]) + .fails() + .code_is(1) + .stderr_contains("split: invalid number of bytes: '-200xd'\n"); + scene + .ucmd() + .args(&["-n", "-200", "file"]) + .fails() + .code_is(1) + .stderr_contains("split: invalid number of chunks: -200\n"); + scene + .ucmd() + .args(&["--number", "-e200", "file"]) + .fails() + .code_is(1) + .stderr_contains("split: invalid number of chunks: -e200\n"); } /// Test for using more than one obsolete lines option (standalone) From 843540d05fafe427ed34d7b8b546357d327d3df5 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Thu, 31 Aug 2023 09:21:59 +0200 Subject: [PATCH 0028/2851] fix a typo --- util/build-gnu.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util/build-gnu.sh b/util/build-gnu.sh index 21a231bc01d..157265f61bf 100755 --- a/util/build-gnu.sh +++ b/util/build-gnu.sh @@ -38,7 +38,7 @@ if test ! -d "${path_GNU}"; then echo "Could not find GNU coreutils (expected at '${path_GNU}')" echo "Run the following to download into the expected path:" echo "git clone --recurse-submodules https://github.com/coreutils/coreutils.git \"${path_GNU}\"" - echo "After downloading GNU coreutils to \"${path_GNU}\" run the following commands to cheout latest release tag" + echo "After downloading GNU coreutils to \"${path_GNU}\" run the following commands to checkout latest release tag" echo "cd \"${path_GNU}\"" echo "git fetch --all --tags" echo "git checkout tags/${release_tag_GNU}" From 5bfe9b19ef775adf40c3e915a731611dbc4a6256 Mon Sep 17 00:00:00 2001 From: zhitkoff Date: Thu, 31 Aug 2023 14:46:56 -0400 Subject: [PATCH 0029/2851] split: avoid using `collect_lossy` + test for invalid UTF8 arguments --- src/uu/split/src/split.rs | 167 +++++++++++++++++++----------------- tests/by-util/test_split.rs | 45 ++++++++++ 2 files changed, 134 insertions(+), 78 deletions(-) diff --git a/src/uu/split/src/split.rs b/src/uu/split/src/split.rs index 7834dfe6821..68692d03c01 100644 --- a/src/uu/split/src/split.rs +++ b/src/uu/split/src/split.rs @@ -13,6 +13,7 @@ use crate::filenames::FilenameIterator; use crate::filenames::SuffixType; use clap::{crate_version, parser::ValueSource, Arg, ArgAction, ArgMatches, Command}; use std::env; +use std::ffi::OsString; use std::fmt; use std::fs::{metadata, File}; use std::io; @@ -52,9 +53,8 @@ const AFTER_HELP: &str = help_section!("after help", "split.md"); #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let args = args.collect_lossy(); - - let (args, obs_lines) = handle_obsolete(&args[..]); + + let (args, obs_lines) = handle_obsolete(args); let matches = uu_app().try_get_matches_from(args)?; @@ -71,91 +71,102 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { /// `split -x300e file` would mean `split -x -l 300 -e file` /// `split -x300e -22 file` would mean `split -x -e -l 22 file` (last obsolete lines option wins) /// following GNU `split` behavior -fn handle_obsolete(args: &[String]) -> (Vec, Option) { +fn handle_obsolete(args: impl uucore::Args) -> (Vec, Option) { let mut obs_lines = None; let mut preceding_long_opt_req_value = false; let mut preceding_short_opt_req_value = false; let filtered_args = args - .iter() - .filter_map(|slice| { - let filter: Option; - // check if the slice is a true short option (and not hyphen prefixed value of an option) - // and if so, a short option that can contain obsolete lines value - if slice.starts_with('-') - && !slice.starts_with("--") - && !preceding_long_opt_req_value - && !preceding_short_opt_req_value - && !slice.starts_with("-a") - && !slice.starts_with("-b") - && !slice.starts_with("-C") - && !slice.starts_with("-l") - && !slice.starts_with("-n") - { - // start of the short option string - // that can have obsolete lines option value in it - // extract numeric part and filter it out - let mut obs_lines_extracted: Vec = vec![]; - let mut obs_lines_end_reached = false; - let filtered_slice: Vec = slice - .chars() - .filter(|c| { - // To correctly process scenario like '-x200a4' - // we need to stop extracting digits once alphabetic character is encountered - // after we already have something in obs_lines_extracted - if c.is_ascii_digit() && !obs_lines_end_reached { - obs_lines_extracted.push(*c); - false - } else { - if !obs_lines_extracted.is_empty() { - obs_lines_end_reached = true; + .filter_map(|os_slice| { + let filter: Option; + if let Some(slice) = os_slice.to_str() { + // check if the slice is a true short option (and not hyphen prefixed value of an option) + // and if so, a short option that can contain obsolete lines value + if slice.starts_with('-') + && !slice.starts_with("--") + && !preceding_long_opt_req_value + && !preceding_short_opt_req_value + && !slice.starts_with("-a") + && !slice.starts_with("-b") + && !slice.starts_with("-C") + && !slice.starts_with("-l") + && !slice.starts_with("-n") + { + // start of the short option string + // that can have obsolete lines option value in it + // extract numeric part and filter it out + let mut obs_lines_extracted: Vec = vec![]; + let mut obs_lines_end_reached = false; + let filtered_slice: Vec = slice + .chars() + .filter(|c| { + // To correctly process scenario like '-x200a4' + // we need to stop extracting digits once alphabetic character is encountered + // after we already have something in obs_lines_extracted + if c.is_ascii_digit() && !obs_lines_end_reached { + obs_lines_extracted.push(*c); + false + } else { + if !obs_lines_extracted.is_empty() { + obs_lines_end_reached = true; + } + true } - true - } - }) - .collect(); + }) + .collect(); - if obs_lines_extracted.is_empty() { - // no obsolete lines value found/extracted - filter = Some(slice.to_owned()); - } else { - // obsolete lines value was extracted - obs_lines = Some(obs_lines_extracted.iter().collect()); - if filtered_slice.get(1).is_some() { - // there were some short options in front of or after obsolete lines value - // i.e. '-xd100' or '-100de' or similar, which after extraction of obsolete lines value - // would look like '-xd' or '-de' or similar - filter = Some(filtered_slice.iter().collect()); + if obs_lines_extracted.is_empty() { + // no obsolete lines value found/extracted + filter = Some(OsString::from(slice)); } else { - filter = None; + // obsolete lines value was extracted + obs_lines = Some(obs_lines_extracted.iter().collect()); + if filtered_slice.get(1).is_some() { + // there were some short options in front of or after obsolete lines value + // i.e. '-xd100' or '-100de' or similar, which after extraction of obsolete lines value + // would look like '-xd' or '-de' or similar + let filtered_slice: String = filtered_slice.iter().collect(); + filter = Some(OsString::from(filtered_slice)); + } else { + filter = None; + } } + } else { + // either not a short option + // or a short option that cannot have obsolete lines value in it + filter = Some(OsString::from(slice)); + } + // capture if current slice is a preceding long option that requires value and does not use '=' to assign that value + // following slice should be treaded as value for this option + // even if it starts with '-' (which would be treated as hyphen prefixed value) + if slice.starts_with("--") { + preceding_long_opt_req_value = &slice[2..] == OPT_BYTES + || &slice[2..] == OPT_LINE_BYTES + || &slice[2..] == OPT_LINES + || &slice[2..] == OPT_ADDITIONAL_SUFFIX + || &slice[2..] == OPT_FILTER + || &slice[2..] == OPT_NUMBER + || &slice[2..] == OPT_SUFFIX_LENGTH; + } + // capture if current slice is a preceding short option that requires value and does not have value in the same slice (value separated by whitespace) + // following slice should be treaded as value for this option + // even if it starts with '-' (which would be treated as hyphen prefixed value) + preceding_short_opt_req_value = slice == "-b" + || slice == "-C" + || slice == "-l" + || slice == "-n" + || slice == "-a"; + // slice is a value + // reset preceding option flags + if !slice.starts_with('-') { + preceding_short_opt_req_value = false; + preceding_long_opt_req_value = false; } } else { - // either not a short option - // or a short option that cannot have obsolete lines value in it - filter = Some(slice.to_owned()); - } - // capture if current slice is a preceding long option that requires value and does not use '=' to assign that value - // following slice should be treaded as value for this option - // even if it starts with '-' (which would be treated as hyphen prefixed value) - if slice.starts_with("--") { - preceding_long_opt_req_value = &slice[2..] == OPT_BYTES - || &slice[2..] == OPT_LINE_BYTES - || &slice[2..] == OPT_LINES - || &slice[2..] == OPT_ADDITIONAL_SUFFIX - || &slice[2..] == OPT_FILTER - || &slice[2..] == OPT_NUMBER - || &slice[2..] == OPT_SUFFIX_LENGTH; - } - // capture if current slice is a preceding short option that requires value and does not have value in the same slice (value separated by whitespace) - // following slice should be treaded as value for this option - // even if it starts with '-' (which would be treated as hyphen prefixed value) - preceding_short_opt_req_value = - slice == "-b" || slice == "-C" || slice == "-l" || slice == "-n" || slice == "-a"; - // slice is a value - // reset preceding option flags - if !slice.starts_with('-') { - preceding_short_opt_req_value = false; - preceding_long_opt_req_value = false; + // Cannot cleanly convert os_slice to UTF-8 + // Do not process and return as-is + // This will cause failure later on, but we should not handle it here + // and let clap panic on invalid UTF-8 argument + filter = Some(os_slice); } // return filter filter diff --git a/tests/by-util/test_split.rs b/tests/by-util/test_split.rs index 466dabda9ab..d24d2cf54d9 100644 --- a/tests/by-util/test_split.rs +++ b/tests/by-util/test_split.rs @@ -9,6 +9,7 @@ use rand::{thread_rng, Rng, SeedableRng}; use regex::Regex; #[cfg(not(windows))] use std::env; +use std::ffi::OsStr; use std::path::Path; use std::{ fs::{read_dir, File}, @@ -1287,3 +1288,47 @@ fn test_split_invalid_input() { .no_stdout() .stderr_contains("split: invalid number of chunks: 0"); } + +/// Test if there are invalid (non UTF-8) in the arguments - unix +/// clap is expected to fail/panic +#[cfg(unix)] +#[test] +fn test_split_non_utf8_argument_unix() { + use std::os::unix::ffi::OsStrExt; + + let (at, mut ucmd) = at_and_ucmd!(); + let name = "test_split_non_utf8_argument"; + let opt = OsStr::from_bytes("--additional-suffix".as_bytes()); + RandomFile::new(&at, name).add_lines(2000); + // Here, the values 0x66 and 0x6f correspond to 'f' and 'o' + // respectively. The value 0x80 is a lone continuation byte, invalid + // in a UTF-8 sequence. + let opt_value = [0x66, 0x6f, 0x80, 0x6f]; + let opt_value = OsStr::from_bytes(&opt_value[..]); + let name = OsStr::from_bytes(name.as_bytes()); + ucmd.args(&[opt, opt_value, name]) + .fails() + .stderr_contains("error: invalid UTF-8 was detected in one or more arguments"); +} + +/// Test if there are invalid (non UTF-8) in the arguments - windows +/// clap is expected to fail/panic +#[cfg(windows)] +#[test] +fn test_split_non_utf8_argument_windows() { + use std::os::windows::prelude::*; + + let (at, mut ucmd) = at_and_ucmd!(); + let name = "test_split_non_utf8_argument"; + let opt = OsStr::from_bytes("--additional-suffix".as_bytes()); + RandomFile::new(&at, name).add_lines(2000); + // Here the values 0x0066 and 0x006f correspond to 'f' and 'o' + // respectively. The value 0xD800 is a lone surrogate half, invalid + // in a UTF-16 sequence. + let opt_value = [0x0066, 0x006f, 0xD800, 0x006f]; + let opt_value = OsString::from_wide(&opt_value[..]); + let name = OsStr::from_bytes(name.as_bytes()); + ucmd.args(&[opt, opt_value, name]) + .fails() + .stderr_contains("error: invalid UTF-8 was detected in one or more arguments"); +} From 271a108fa9e1ea75ed8c6e42c2a207a6da935c81 Mon Sep 17 00:00:00 2001 From: zhitkoff Date: Thu, 31 Aug 2023 15:37:42 -0400 Subject: [PATCH 0030/2851] split: formatting --- src/uu/split/src/split.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/uu/split/src/split.rs b/src/uu/split/src/split.rs index 68692d03c01..d76bfb2de74 100644 --- a/src/uu/split/src/split.rs +++ b/src/uu/split/src/split.rs @@ -53,7 +53,6 @@ const AFTER_HELP: &str = help_section!("after help", "split.md"); #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let (args, obs_lines) = handle_obsolete(args); let matches = uu_app().try_get_matches_from(args)?; From d2812cbbc387b7dc3ad8f5f31863aad09ce1e6ad Mon Sep 17 00:00:00 2001 From: zhitkoff Date: Thu, 31 Aug 2023 16:04:44 -0400 Subject: [PATCH 0031/2851] split: disable windows test for invalid UTF8 --- tests/by-util/test_split.rs | 46 +++++++++++++++++++------------------ 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/tests/by-util/test_split.rs b/tests/by-util/test_split.rs index d24d2cf54d9..aef9ea040ed 100644 --- a/tests/by-util/test_split.rs +++ b/tests/by-util/test_split.rs @@ -9,7 +9,6 @@ use rand::{thread_rng, Rng, SeedableRng}; use regex::Regex; #[cfg(not(windows))] use std::env; -use std::ffi::OsStr; use std::path::Path; use std::{ fs::{read_dir, File}, @@ -1294,6 +1293,7 @@ fn test_split_invalid_input() { #[cfg(unix)] #[test] fn test_split_non_utf8_argument_unix() { + use std::ffi::OsStr; use std::os::unix::ffi::OsStrExt; let (at, mut ucmd) = at_and_ucmd!(); @@ -1311,24 +1311,26 @@ fn test_split_non_utf8_argument_unix() { .stderr_contains("error: invalid UTF-8 was detected in one or more arguments"); } -/// Test if there are invalid (non UTF-8) in the arguments - windows -/// clap is expected to fail/panic -#[cfg(windows)] -#[test] -fn test_split_non_utf8_argument_windows() { - use std::os::windows::prelude::*; - - let (at, mut ucmd) = at_and_ucmd!(); - let name = "test_split_non_utf8_argument"; - let opt = OsStr::from_bytes("--additional-suffix".as_bytes()); - RandomFile::new(&at, name).add_lines(2000); - // Here the values 0x0066 and 0x006f correspond to 'f' and 'o' - // respectively. The value 0xD800 is a lone surrogate half, invalid - // in a UTF-16 sequence. - let opt_value = [0x0066, 0x006f, 0xD800, 0x006f]; - let opt_value = OsString::from_wide(&opt_value[..]); - let name = OsStr::from_bytes(name.as_bytes()); - ucmd.args(&[opt, opt_value, name]) - .fails() - .stderr_contains("error: invalid UTF-8 was detected in one or more arguments"); -} +// Test if there are invalid (non UTF-8) in the arguments - windows +// clap is expected to fail/panic +// comment it out for now +// #[cfg(windows)] +// #[test] +// fn test_split_non_utf8_argument_windows() { +// use std::ffi::OsString; +// use std::os::windows::prelude::*; + +// let (at, mut ucmd) = at_and_ucmd!(); +// let name = "test_split_non_utf8_argument"; +// let opt = OsStr::from_bytes("--additional-suffix".as_bytes()); +// RandomFile::new(&at, name).add_lines(2000); +// // Here the values 0x0066 and 0x006f correspond to 'f' and 'o' +// // respectively. The value 0xD800 is a lone surrogate half, invalid +// // in a UTF-16 sequence. +// let opt_value = [0x0066, 0x006f, 0xD800, 0x006f]; +// let opt_value = OsString::from_wide(&opt_value[..]); +// let name = OsStr::from_bytes(name.as_bytes()); +// ucmd.args(&[opt, opt_value, name]) +// .fails() +// .stderr_contains("error: invalid UTF-8 was detected in one or more arguments"); +// } From e597189be7555164fc7f57df0b03f1ae788901ea Mon Sep 17 00:00:00 2001 From: zhitkoff Date: Thu, 31 Aug 2023 20:48:44 -0400 Subject: [PATCH 0032/2851] split: fixed windows test for invalid unicode args --- tests/by-util/test_split.rs | 47 ++++++++++++++++++------------------- 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/tests/by-util/test_split.rs b/tests/by-util/test_split.rs index aef9ea040ed..965f2733e22 100644 --- a/tests/by-util/test_split.rs +++ b/tests/by-util/test_split.rs @@ -1290,8 +1290,8 @@ fn test_split_invalid_input() { /// Test if there are invalid (non UTF-8) in the arguments - unix /// clap is expected to fail/panic -#[cfg(unix)] #[test] +#[cfg(unix)] fn test_split_non_utf8_argument_unix() { use std::ffi::OsStr; use std::os::unix::ffi::OsStrExt; @@ -1311,26 +1311,25 @@ fn test_split_non_utf8_argument_unix() { .stderr_contains("error: invalid UTF-8 was detected in one or more arguments"); } -// Test if there are invalid (non UTF-8) in the arguments - windows -// clap is expected to fail/panic -// comment it out for now -// #[cfg(windows)] -// #[test] -// fn test_split_non_utf8_argument_windows() { -// use std::ffi::OsString; -// use std::os::windows::prelude::*; - -// let (at, mut ucmd) = at_and_ucmd!(); -// let name = "test_split_non_utf8_argument"; -// let opt = OsStr::from_bytes("--additional-suffix".as_bytes()); -// RandomFile::new(&at, name).add_lines(2000); -// // Here the values 0x0066 and 0x006f correspond to 'f' and 'o' -// // respectively. The value 0xD800 is a lone surrogate half, invalid -// // in a UTF-16 sequence. -// let opt_value = [0x0066, 0x006f, 0xD800, 0x006f]; -// let opt_value = OsString::from_wide(&opt_value[..]); -// let name = OsStr::from_bytes(name.as_bytes()); -// ucmd.args(&[opt, opt_value, name]) -// .fails() -// .stderr_contains("error: invalid UTF-8 was detected in one or more arguments"); -// } +/// Test if there are invalid (non UTF-8) in the arguments - windows +/// clap is expected to fail/panic +#[test] +#[cfg(windows)] +fn test_split_non_utf8_argument_windows() { + use std::ffi::OsString; + use std::os::windows::ffi::OsStringExt; + + let (at, mut ucmd) = at_and_ucmd!(); + let name = "test_split_non_utf8_argument"; + let opt = OsString::from("--additional-suffix"); + RandomFile::new(&at, name).add_lines(2000); + // Here the values 0x0066 and 0x006f correspond to 'f' and 'o' + // respectively. The value 0xD800 is a lone surrogate half, invalid + // in a UTF-16 sequence. + let opt_value = [0x0066, 0x006f, 0xD800, 0x006f]; + let opt_value = OsString::from_wide(&opt_value[..]); + let name = OsString::from(name); + ucmd.args(&[opt, opt_value, name]) + .fails() + .stderr_contains("error: invalid UTF-8 was detected in one or more arguments"); +} From f0923094d21c34e32f77b55400f2063a446eff05 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Mon, 4 Sep 2023 07:18:18 +0200 Subject: [PATCH 0033/2851] Bump MSRV to 1.70 --- .clippy.toml | 2 +- .github/workflows/CICD.yml | 2 +- Cargo.toml | 2 +- README.md | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.clippy.toml b/.clippy.toml index bee70857bd1..814e40b6960 100644 --- a/.clippy.toml +++ b/.clippy.toml @@ -1,2 +1,2 @@ -msrv = "1.64.0" +msrv = "1.70.0" cognitive-complexity-threshold = 10 diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index fe72de11067..d387f566ffa 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -11,7 +11,7 @@ env: PROJECT_NAME: coreutils PROJECT_DESC: "Core universal (cross-platform) utilities" PROJECT_AUTH: "uutils" - RUST_MIN_SRV: "1.64.0" + RUST_MIN_SRV: "1.70.0" # * style job configuration STYLE_FAIL_ON_FAULT: true ## (bool) fail the build if a style job contains a fault (error or warning); may be overridden on a per-job basis diff --git a/Cargo.toml b/Cargo.toml index 320aa0f4307..8d658c2994c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,7 @@ repository = "https://github.com/uutils/coreutils" readme = "README.md" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] -rust-version = "1.64.0" +rust-version = "1.70.0" edition = "2021" build = "build.rs" diff --git a/README.md b/README.md index c5b4609c924..4f341638b3f 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ [![dependency status](https://deps.rs/repo/github/uutils/coreutils/status.svg)](https://deps.rs/repo/github/uutils/coreutils) [![CodeCov](https://codecov.io/gh/uutils/coreutils/branch/master/graph/badge.svg)](https://codecov.io/gh/uutils/coreutils) -![MSRV](https://img.shields.io/badge/MSRV-1.64.0-brightgreen) +![MSRV](https://img.shields.io/badge/MSRV-1.70.0-brightgreen) @@ -71,7 +71,7 @@ the [coreutils docs](https://github.com/uutils/uutils.github.io) repository. ### Rust Version uutils follows Rust's release channels and is tested against stable, beta and -nightly. The current Minimum Supported Rust Version (MSRV) is `1.64.0`. +nightly. The current Minimum Supported Rust Version (MSRV) is `1.70.0`. ## Building From 8920ac0123ceaec5a301e0e171d425b4bcbecd6f Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Mon, 4 Sep 2023 07:26:23 +0200 Subject: [PATCH 0034/2851] split: fix clippy warning in test --- tests/by-util/test_split.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/by-util/test_split.rs b/tests/by-util/test_split.rs index f3317d4c7a4..0b7bbfec6dd 100644 --- a/tests/by-util/test_split.rs +++ b/tests/by-util/test_split.rs @@ -871,7 +871,7 @@ fn test_short_combination() { assert_eq!(at.read("x00"), "a"); assert_eq!(at.read("x01"), "b"); assert_eq!(at.read("x02"), "c"); - assert_eq!(at.file_exists("x03"), false); + assert!(!at.file_exists("x03")); } /// Test for the last effective suffix, ignoring all others - numeric long last From c4c9c4730c5ebb830c995c42c590f1a9b2aba319 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 24 Aug 2023 18:30:39 +0000 Subject: [PATCH 0035/2851] chore(deps): update rust crate clap_complete to 4.4 --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f4885faa05c..13008ab9a60 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -286,9 +286,9 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.3.0" +version = "4.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a04ddfaacc3bc9e6ea67d024575fafc2a813027cf374b8f24f7bc233c6b6be12" +checksum = "586a385f7ef2f8b4d86bddaa0c094794e7ccbfe5ffef1f434fe928143fc783a5" dependencies = [ "clap", ] diff --git a/Cargo.toml b/Cargo.toml index 8d658c2994c..90ee3fb4ac7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -269,7 +269,7 @@ chrono = { version = "^0.4.28", default-features = false, features = [ "clock", ] } clap = { version = "4.3", features = ["wrap_help", "cargo"] } -clap_complete = "4.3" +clap_complete = "4.4" clap_mangen = "0.2" compare = "0.1.0" coz = { version = "0.1.3" } From 4992cb9b8616af5dd9fe52daa1e61641e66f37f4 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Mon, 4 Sep 2023 10:35:17 +0200 Subject: [PATCH 0036/2851] Use std::io::IsTerminal instead of is-terminal --- Cargo.lock | 8 -------- Cargo.toml | 2 -- src/uu/cat/Cargo.toml | 1 - src/uu/cat/src/cat.rs | 3 +-- src/uu/cut/Cargo.toml | 1 - src/uu/cut/src/cut.rs | 3 +-- src/uu/ls/Cargo.toml | 1 - src/uu/ls/src/ls.rs | 3 +-- src/uu/more/Cargo.toml | 1 - src/uu/more/src/more.rs | 3 +-- src/uu/nohup/Cargo.toml | 1 - src/uu/nohup/src/nohup.rs | 3 +-- src/uu/tail/Cargo.toml | 1 - src/uu/tail/src/args.rs | 2 +- src/uu/tty/Cargo.toml | 1 - src/uu/tty/src/tty.rs | 3 +-- tests/by-util/test_more.rs | 2 +- 17 files changed, 8 insertions(+), 31 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f4885faa05c..55879e5850e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -389,7 +389,6 @@ dependencies = [ "filetime", "glob", "hex-literal", - "is-terminal", "libc", "nix", "once_cell", @@ -2311,7 +2310,6 @@ name = "uu_cat" version = "0.0.21" dependencies = [ "clap", - "is-terminal", "nix", "thiserror", "uucore", @@ -2411,7 +2409,6 @@ version = "0.0.21" dependencies = [ "bstr", "clap", - "is-terminal", "memchr", "uucore", ] @@ -2679,7 +2676,6 @@ dependencies = [ "chrono", "clap", "glob", - "is-terminal", "lscolors", "number_prefix", "once_cell", @@ -2732,7 +2728,6 @@ version = "0.0.21" dependencies = [ "clap", "crossterm", - "is-terminal", "nix", "unicode-segmentation", "unicode-width", @@ -2773,7 +2768,6 @@ name = "uu_nohup" version = "0.0.21" dependencies = [ "clap", - "is-terminal", "libc", "uucore", ] @@ -3072,7 +3066,6 @@ version = "0.0.21" dependencies = [ "clap", "fundu", - "is-terminal", "libc", "memchr", "notify", @@ -3162,7 +3155,6 @@ name = "uu_tty" version = "0.0.21" dependencies = [ "clap", - "is-terminal", "nix", "uucore", ] diff --git a/Cargo.toml b/Cargo.toml index 8d658c2994c..405ed08f8ea 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -286,7 +286,6 @@ gcd = "2.3" glob = "0.3.1" half = "2.2" indicatif = "0.17" -is-terminal = "0.4.9" itertools = "0.11.0" libc = "0.2.147" lscolors = { version = "0.15.0", default-features = false, features = [ @@ -491,7 +490,6 @@ time = { workspace = true, features = ["local-offset"] } unindent = "0.2" uucore = { workspace = true, features = ["entries", "process", "signals"] } walkdir = { workspace = true } -is-terminal = { workspace = true } hex-literal = "0.4.1" rstest = { workspace = true } diff --git a/src/uu/cat/Cargo.toml b/src/uu/cat/Cargo.toml index d67cd1b254d..166e1823b58 100644 --- a/src/uu/cat/Cargo.toml +++ b/src/uu/cat/Cargo.toml @@ -17,7 +17,6 @@ path = "src/cat.rs" [dependencies] clap = { workspace = true } thiserror = { workspace = true } -is-terminal = { workspace = true } uucore = { workspace = true, features = ["fs", "pipes"] } [target.'cfg(unix)'.dependencies] diff --git a/src/uu/cat/src/cat.rs b/src/uu/cat/src/cat.rs index 8ce8052517b..d49f4aa0707 100644 --- a/src/uu/cat/src/cat.rs +++ b/src/uu/cat/src/cat.rs @@ -7,9 +7,8 @@ // last synced with: cat (GNU coreutils) 8.13 use clap::{crate_version, Arg, ArgAction, Command}; -use is_terminal::IsTerminal; use std::fs::{metadata, File}; -use std::io::{self, Read, Write}; +use std::io::{self, IsTerminal, Read, Write}; use thiserror::Error; use uucore::display::Quotable; use uucore::error::UResult; diff --git a/src/uu/cut/Cargo.toml b/src/uu/cut/Cargo.toml index affeb38058f..a55ce7d5862 100644 --- a/src/uu/cut/Cargo.toml +++ b/src/uu/cut/Cargo.toml @@ -19,7 +19,6 @@ clap = { workspace = true } uucore = { workspace = true, features = ["ranges"] } memchr = { workspace = true } bstr = { workspace = true } -is-terminal = { workspace = true } [[bin]] name = "cut" diff --git a/src/uu/cut/src/cut.rs b/src/uu/cut/src/cut.rs index 89128175354..4d3145c059c 100644 --- a/src/uu/cut/src/cut.rs +++ b/src/uu/cut/src/cut.rs @@ -7,9 +7,8 @@ use bstr::io::BufReadExt; use clap::{crate_version, Arg, ArgAction, Command}; -use is_terminal::IsTerminal; use std::fs::File; -use std::io::{stdin, stdout, BufReader, BufWriter, Read, Write}; +use std::io::{stdin, stdout, BufReader, BufWriter, IsTerminal, Read, Write}; use std::path::Path; use uucore::display::Quotable; use uucore::error::{FromIo, UResult, USimpleError}; diff --git a/src/uu/ls/Cargo.toml b/src/uu/ls/Cargo.toml index b1114644d40..f3fc2eb6bfd 100644 --- a/src/uu/ls/Cargo.toml +++ b/src/uu/ls/Cargo.toml @@ -25,7 +25,6 @@ glob = { workspace = true } lscolors = { workspace = true } uucore = { workspace = true, features = ["entries", "fs"] } once_cell = { workspace = true } -is-terminal = { workspace = true } selinux = { workspace = true, optional = true } [[bin]] diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 1f5165e8303..652b978a5b0 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -10,12 +10,11 @@ use clap::{ crate_version, Arg, ArgAction, Command, }; use glob::{MatchOptions, Pattern}; -use is_terminal::IsTerminal; use lscolors::LsColors; use number_prefix::NumberPrefix; use once_cell::unsync::OnceCell; -use std::collections::HashSet; use std::num::IntErrorKind; +use std::{collections::HashSet, io::IsTerminal}; #[cfg(windows)] use std::os::windows::fs::MetadataExt; diff --git a/src/uu/more/Cargo.toml b/src/uu/more/Cargo.toml index ef224013307..bc0f2521794 100644 --- a/src/uu/more/Cargo.toml +++ b/src/uu/more/Cargo.toml @@ -18,7 +18,6 @@ path = "src/more.rs" clap = { workspace = true } uucore = { workspace = true } crossterm = { workspace = true } -is-terminal = { workspace = true } unicode-width = { workspace = true } unicode-segmentation = { workspace = true } diff --git a/src/uu/more/src/more.rs b/src/uu/more/src/more.rs index 75cf79c07a9..02ed0feea20 100644 --- a/src/uu/more/src/more.rs +++ b/src/uu/more/src/more.rs @@ -7,7 +7,7 @@ use std::{ fs::File, - io::{stdin, stdout, BufReader, Read, Stdout, Write}, + io::{stdin, stdout, BufReader, IsTerminal, Read, Stdout, Write}, path::Path, time::Duration, }; @@ -22,7 +22,6 @@ use crossterm::{ terminal::{self, Clear, ClearType}, }; -use is_terminal::IsTerminal; use unicode_segmentation::UnicodeSegmentation; use unicode_width::UnicodeWidthStr; use uucore::display::Quotable; diff --git a/src/uu/nohup/Cargo.toml b/src/uu/nohup/Cargo.toml index 2a454b9f433..9fbf125bf5b 100644 --- a/src/uu/nohup/Cargo.toml +++ b/src/uu/nohup/Cargo.toml @@ -17,7 +17,6 @@ path = "src/nohup.rs" [dependencies] clap = { workspace = true } libc = { workspace = true } -is-terminal = { workspace = true } uucore = { workspace = true, features = ["fs"] } [[bin]] diff --git a/src/uu/nohup/src/nohup.rs b/src/uu/nohup/src/nohup.rs index fdbed93956b..c64f7bf71ba 100644 --- a/src/uu/nohup/src/nohup.rs +++ b/src/uu/nohup/src/nohup.rs @@ -6,14 +6,13 @@ // spell-checker:ignore (ToDO) execvp SIGHUP cproc vprocmgr cstrs homeout use clap::{crate_version, Arg, ArgAction, Command}; -use is_terminal::IsTerminal; use libc::{c_char, dup2, execvp, signal}; use libc::{SIGHUP, SIG_IGN}; use std::env; use std::ffi::CString; use std::fmt::{Display, Formatter}; use std::fs::{File, OpenOptions}; -use std::io::Error; +use std::io::{Error, IsTerminal}; use std::os::unix::prelude::*; use std::path::{Path, PathBuf}; use uucore::display::Quotable; diff --git a/src/uu/tail/Cargo.toml b/src/uu/tail/Cargo.toml index 792b7fcf178..5ff532e60fc 100644 --- a/src/uu/tail/Cargo.toml +++ b/src/uu/tail/Cargo.toml @@ -22,7 +22,6 @@ memchr = { workspace = true } notify = { workspace = true } uucore = { workspace = true } same-file = { workspace = true } -is-terminal = { workspace = true } fundu = { workspace = true } [target.'cfg(windows)'.dependencies] diff --git a/src/uu/tail/src/args.rs b/src/uu/tail/src/args.rs index 795652f2650..388842a1422 100644 --- a/src/uu/tail/src/args.rs +++ b/src/uu/tail/src/args.rs @@ -10,9 +10,9 @@ use crate::{parse, platform, Quotable}; use clap::{crate_version, value_parser}; use clap::{Arg, ArgAction, ArgMatches, Command}; use fundu::{DurationParser, SaturatingInto}; -use is_terminal::IsTerminal; use same_file::Handle; use std::ffi::OsString; +use std::io::IsTerminal; use std::time::Duration; use uucore::error::{UResult, USimpleError, UUsageError}; use uucore::parse_size::{parse_size, ParseSizeError}; diff --git a/src/uu/tty/Cargo.toml b/src/uu/tty/Cargo.toml index fde233250ac..917d04c106a 100644 --- a/src/uu/tty/Cargo.toml +++ b/src/uu/tty/Cargo.toml @@ -17,7 +17,6 @@ path = "src/tty.rs" [dependencies] clap = { workspace = true } nix = { workspace = true, features = ["term"] } -is-terminal = { workspace = true } uucore = { workspace = true, features = ["fs"] } [[bin]] diff --git a/src/uu/tty/src/tty.rs b/src/uu/tty/src/tty.rs index 96d851d3738..efda4a7becc 100644 --- a/src/uu/tty/src/tty.rs +++ b/src/uu/tty/src/tty.rs @@ -8,8 +8,7 @@ // spell-checker:ignore (ToDO) ttyname filedesc use clap::{crate_version, Arg, ArgAction, Command}; -use is_terminal::IsTerminal; -use std::io::Write; +use std::io::{IsTerminal, Write}; use std::os::unix::io::AsRawFd; use uucore::error::{set_exit_code, UResult}; use uucore::{format_usage, help_about, help_usage}; diff --git a/tests/by-util/test_more.rs b/tests/by-util/test_more.rs index b6ded2298d8..e80020d3996 100644 --- a/tests/by-util/test_more.rs +++ b/tests/by-util/test_more.rs @@ -3,7 +3,7 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. use crate::common::util::TestScenario; -use is_terminal::IsTerminal; +use std::io::IsTerminal; #[test] fn test_more_no_arg() { From 044e09786ea2b1a6182d5a751aa8d0db66ff19a2 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 4 Sep 2023 15:42:41 +0000 Subject: [PATCH 0037/2851] chore(deps): update actions/checkout action to v4 --- .github/workflows/CICD.yml | 36 +++++++++++++++--------------- .github/workflows/CheckScripts.yml | 4 ++-- .github/workflows/FixPR.yml | 4 ++-- .github/workflows/GnuTests.yml | 8 +++---- .github/workflows/android.yml | 2 +- .github/workflows/freebsd.yml | 4 ++-- 6 files changed, 29 insertions(+), 29 deletions(-) diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index d387f566ffa..27378cd9672 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -30,7 +30,7 @@ jobs: name: Style/cargo-deny runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: EmbarkStudios/cargo-deny-action@v1 style_deps: @@ -47,7 +47,7 @@ jobs: - { os: macos-latest , features: "feat_Tier1,feat_require_unix,feat_require_unix_utmpx" } - { os: windows-latest , features: feat_os_windows } steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@nightly ## note: requires 'nightly' toolchain b/c `cargo-udeps` uses the `rustc` '-Z save-analysis' option ## * ... ref: @@ -91,7 +91,7 @@ jobs: job: - { os: ubuntu-latest , features: feat_os_unix } steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@master with: toolchain: stable @@ -131,7 +131,7 @@ jobs: env: RUN_FOR: 60 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@nightly - name: Install `cargo-fuzz` run: cargo install cargo-fuzz @@ -182,7 +182,7 @@ jobs: - { os: macos-latest , features: feat_os_macos } - { os: windows-latest , features: feat_os_windows } steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@master with: toolchain: stable @@ -238,7 +238,7 @@ jobs: job: - { os: ubuntu-latest , features: feat_os_unix } steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Initialize workflow variables id: vars shell: bash @@ -292,7 +292,7 @@ jobs: # - { os: macos-latest , features: feat_os_macos } # - { os: windows-latest , features: feat_os_windows } steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@master with: toolchain: stable @@ -345,7 +345,7 @@ jobs: job: - { os: ubuntu-latest , features: feat_os_unix } steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@master with: toolchain: ${{ env.RUST_MIN_SRV }} @@ -413,7 +413,7 @@ jobs: job: - { os: ubuntu-latest , features: feat_os_unix } steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - uses: Swatinem/rust-cache@v2 - name: "`cargo update` testing" @@ -436,7 +436,7 @@ jobs: job: - { os: ubuntu-latest , features: feat_os_unix } steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - uses: taiki-e/install-action@nextest - uses: Swatinem/rust-cache@v2 @@ -490,7 +490,7 @@ jobs: - { os: macos-latest , features: feat_os_macos } - { os: windows-latest , features: feat_os_windows } steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - uses: taiki-e/install-action@nextest - uses: Swatinem/rust-cache@v2 @@ -517,7 +517,7 @@ jobs: - { os: macos-latest , features: feat_os_macos } - { os: windows-latest , features: feat_os_windows } steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@nightly - uses: taiki-e/install-action@nextest - uses: Swatinem/rust-cache@v2 @@ -541,7 +541,7 @@ jobs: job: - { os: ubuntu-latest , features: feat_os_unix } steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - uses: Swatinem/rust-cache@v2 - name: Run sccache-cache @@ -661,7 +661,7 @@ jobs: - { os: windows-latest , target: x86_64-pc-windows-gnu , features: feat_os_windows } ## note: requires rust >= 1.43.0 to link correctly - { os: windows-latest , target: x86_64-pc-windows-msvc , features: feat_os_windows } steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@master with: toolchain: ${{ env.RUST_MIN_SRV }} @@ -920,7 +920,7 @@ jobs: run: | ## VARs setup echo "TEST_SUMMARY_FILE=busybox-result.json" >> $GITHUB_OUTPUT - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: Swatinem/rust-cache@v2 - name: Run sccache-cache uses: mozilla-actions/sccache-action@v0.0.3 @@ -1000,7 +1000,7 @@ jobs: outputs() { step_id="${{ github.action }}"; for var in "$@" ; do echo steps.${step_id}.outputs.${var}="${!var}"; echo "${var}=${!var}" >> $GITHUB_OUTPUT; done; } TEST_SUMMARY_FILE="toybox-result.json" outputs TEST_SUMMARY_FILE - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@master with: toolchain: ${{ env.RUST_MIN_SRV }} @@ -1071,7 +1071,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Clone repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Check run: npx --yes @taplo/cli fmt --check @@ -1091,7 +1091,7 @@ jobs: - { os: macos-latest , features: macos, toolchain: nightly } - { os: windows-latest , features: windows, toolchain: nightly-x86_64-pc-windows-gnu } steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@master with: toolchain: ${{ matrix.job.toolchain }} diff --git a/.github/workflows/CheckScripts.yml b/.github/workflows/CheckScripts.yml index e1b3b24d205..98ae6cb75ab 100644 --- a/.github/workflows/CheckScripts.yml +++ b/.github/workflows/CheckScripts.yml @@ -29,7 +29,7 @@ jobs: permissions: contents: read steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Run ShellCheck uses: ludeeus/action-shellcheck@master env: @@ -50,7 +50,7 @@ jobs: contents: write pull-requests: write steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Setup shfmt uses: mfinelli/setup-shfmt@v2 - name: Run shfmt diff --git a/.github/workflows/FixPR.yml b/.github/workflows/FixPR.yml index 97b0be34afa..7f5e5234d8b 100644 --- a/.github/workflows/FixPR.yml +++ b/.github/workflows/FixPR.yml @@ -26,7 +26,7 @@ jobs: job: - { os: ubuntu-latest , features: feat_os_unix } steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Initialize job variables id: vars shell: bash @@ -85,7 +85,7 @@ jobs: job: - { os: ubuntu-latest , features: feat_os_unix } steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Initialize job variables id: vars shell: bash diff --git a/.github/workflows/GnuTests.yml b/.github/workflows/GnuTests.yml index ccb5a6f7495..61f30eba4c1 100644 --- a/.github/workflows/GnuTests.yml +++ b/.github/workflows/GnuTests.yml @@ -55,7 +55,7 @@ jobs: TEST_FULL_SUMMARY_FILE='gnu-full-result.json' outputs SUITE_LOG_FILE ROOT_SUITE_LOG_FILE TEST_FILESET_PREFIX TEST_FILESET_SUFFIX TEST_LOGS_GLOB TEST_SUMMARY_FILE TEST_FULL_SUMMARY_FILE - name: Checkout code (uutil) - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: path: '${{ steps.vars.outputs.path_UUTILS }}' - uses: dtolnay/rust-toolchain@master @@ -66,7 +66,7 @@ jobs: with: workspaces: "./${{ steps.vars.outputs.path_UUTILS }} -> target" - name: Checkout code (GNU coreutils) - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: repository: 'coreutils/coreutils' path: '${{ steps.vars.outputs.path_GNU }}' @@ -307,11 +307,11 @@ jobs: runs-on: ubuntu-20.04 steps: - name: Checkout code uutil - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: path: 'uutils' - name: Checkout GNU coreutils - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: repository: 'coreutils/coreutils' path: 'gnu' diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index a6929b171cc..5834aceffe8 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -26,7 +26,7 @@ jobs: env: TERMUX: v0.118.0 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Restore AVD cache uses: actions/cache/restore@v3 id: avd-cache diff --git a/.github/workflows/freebsd.yml b/.github/workflows/freebsd.yml index 095ec323098..5af3da320a7 100644 --- a/.github/workflows/freebsd.yml +++ b/.github/workflows/freebsd.yml @@ -30,7 +30,7 @@ jobs: SCCACHE_GHA_ENABLED: "true" RUSTC_WRAPPER: "sccache" steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: Swatinem/rust-cache@v2 - name: Run sccache-cache uses: mozilla-actions/sccache-action@v0.0.3 @@ -120,7 +120,7 @@ jobs: SCCACHE_GHA_ENABLED: "true" RUSTC_WRAPPER: "sccache" steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: Swatinem/rust-cache@v2 - name: Run sccache-cache uses: mozilla-actions/sccache-action@v0.0.3 From 8c1696084c08488146d6068a092ada14a6cf47b8 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 4 Sep 2023 15:47:49 +0000 Subject: [PATCH 0038/2851] chore(deps): update rust crate clap to 4.4 --- Cargo.lock | 18 ++++++++---------- Cargo.toml | 2 +- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 13008ab9a60..c3e7ae92f2a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -34,16 +34,15 @@ dependencies = [ [[package]] name = "anstream" -version = "0.3.2" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163" +checksum = "b1f58811cfac344940f1a400b6e6231ce35171f614f26439e80f8c1465c5cc0c" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", - "is-terminal", "utf8parse", ] @@ -73,9 +72,9 @@ dependencies = [ [[package]] name = "anstyle-wincon" -version = "1.0.1" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180abfa45703aebe0093f79badacc01b8fd4ea2e35118747e5811127f926e188" +checksum = "58f54d10c6dfa51283a066ceab3ec1ab78d13fae00aa49243a45e4571fb79dfd" dependencies = [ "anstyle", "windows-sys 0.48.0", @@ -263,23 +262,22 @@ dependencies = [ [[package]] name = "clap" -version = "4.3.21" +version = "4.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c27cdf28c0f604ba3f512b0c9a409f8de8513e4816705deb0498b627e7c3a3fd" +checksum = "6a13b88d2c62ff462f88e4a121f17a82c1af05693a2f192b5c38d14de73c19f6" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.3.21" +version = "4.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08a9f1ab5e9f01a9b81f202e8562eb9a10de70abf9eaeac1be465c28b75aa4aa" +checksum = "2bb9faaa7c2ef94b2743a21f5a29e6f0010dff4caa69ac8e9d6cf8b6fa74da08" dependencies = [ "anstream", "anstyle", "clap_lex", - "once_cell", "strsim", "terminal_size", ] diff --git a/Cargo.toml b/Cargo.toml index 90ee3fb4ac7..97edb75dd7a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -268,7 +268,7 @@ chrono = { version = "^0.4.28", default-features = false, features = [ "alloc", "clock", ] } -clap = { version = "4.3", features = ["wrap_help", "cargo"] } +clap = { version = "4.4", features = ["wrap_help", "cargo"] } clap_complete = "4.4" clap_mangen = "0.2" compare = "0.1.0" From 636c2bb7ae9444551d900d50829eb87414a23f8f Mon Sep 17 00:00:00 2001 From: zhitkoff Date: Mon, 4 Sep 2023 12:05:26 -0400 Subject: [PATCH 0039/2851] uucore: parse_size_max and split --- src/uu/split/src/split.rs | 28 ++++++++-------------- src/uucore/src/lib/parser/parse_size.rs | 32 +++++++++++++++++++++++-- 2 files changed, 40 insertions(+), 20 deletions(-) diff --git a/src/uu/split/src/split.rs b/src/uu/split/src/split.rs index d76bfb2de74..e39f6e93ffe 100644 --- a/src/uu/split/src/split.rs +++ b/src/uu/split/src/split.rs @@ -21,7 +21,7 @@ use std::io::{stdin, BufRead, BufReader, BufWriter, ErrorKind, Read, Write}; use std::path::Path; use uucore::display::Quotable; use uucore::error::{FromIo, UIoError, UResult, USimpleError, UUsageError}; -use uucore::parse_size::{parse_size, ParseSizeError}; +use uucore::parse_size::{parse_size_max, ParseSizeError}; use uucore::uio_error; use uucore::{format_usage, help_about, help_section, help_usage}; @@ -419,8 +419,7 @@ impl NumberType { let parts: Vec<&str> = s.split('/').collect(); match &parts[..] { [n_str] => { - let num_chunks = n_str - .parse() + let num_chunks = parse_size_max(n_str) .map_err(|_| NumberTypeError::NumberOfChunks(n_str.to_string()))?; if num_chunks > 0 { Ok(Self::Bytes(num_chunks)) @@ -429,32 +428,26 @@ impl NumberType { } } ["l", n_str] => { - let num_chunks = n_str - .parse() + let num_chunks = parse_size_max(n_str) .map_err(|_| NumberTypeError::NumberOfChunks(n_str.to_string()))?; Ok(Self::Lines(num_chunks)) } ["l", k_str, n_str] => { - let num_chunks = n_str - .parse() + let num_chunks = parse_size_max(n_str) .map_err(|_| NumberTypeError::NumberOfChunks(n_str.to_string()))?; - let chunk_number = k_str - .parse() + let chunk_number = parse_size_max(k_str) .map_err(|_| NumberTypeError::ChunkNumber(k_str.to_string()))?; Ok(Self::KthLines(chunk_number, num_chunks)) } ["r", n_str] => { - let num_chunks = n_str - .parse() + let num_chunks = parse_size_max(n_str) .map_err(|_| NumberTypeError::NumberOfChunks(n_str.to_string()))?; Ok(Self::RoundRobin(num_chunks)) } ["r", k_str, n_str] => { - let num_chunks = n_str - .parse() + let num_chunks = parse_size_max(n_str) .map_err(|_| NumberTypeError::NumberOfChunks(n_str.to_string()))?; - let chunk_number = k_str - .parse() + let chunk_number = parse_size_max(k_str) .map_err(|_| NumberTypeError::ChunkNumber(k_str.to_string()))?; Ok(Self::KthRoundRobin(chunk_number, num_chunks)) } @@ -523,7 +516,7 @@ impl Strategy { error: fn(ParseSizeError) -> StrategyError, ) -> Result { let s = matches.get_one::(option).unwrap(); - let n = parse_size(s).map_err(error)?; + let n = parse_size_max(s).map_err(error)?; if n > 0 { Ok(strategy(n)) } else { @@ -542,7 +535,7 @@ impl Strategy { matches.value_source(OPT_NUMBER) == Some(ValueSource::CommandLine), ) { (Some(v), false, false, false, false) => { - let v = parse_size(v).map_err(|_| { + let v = parse_size_max(v).map_err(|_| { StrategyError::Lines(ParseSizeError::ParseFailure(v.to_string())) })?; Ok(Self::Lines(v)) @@ -687,7 +680,6 @@ impl Settings { if additional_suffix.contains('/') { return Err(SettingsError::SuffixContainsSeparator(additional_suffix)); } - let strategy = Strategy::from(matches, obs_lines).map_err(SettingsError::Strategy)?; let (suffix_type, suffix_start) = suffix_type_from(matches)?; let suffix_length_str = matches.get_one::(OPT_SUFFIX_LENGTH).unwrap(); diff --git a/src/uucore/src/lib/parser/parse_size.rs b/src/uucore/src/lib/parser/parse_size.rs index 5f64afcd8a5..63039e6093b 100644 --- a/src/uucore/src/lib/parser/parse_size.rs +++ b/src/uucore/src/lib/parser/parse_size.rs @@ -7,6 +7,7 @@ use std::error::Error; use std::fmt; +use std::num::IntErrorKind; use crate::display::Quotable; @@ -201,8 +202,10 @@ impl<'parser> Parser<'parser> { radix: u32, original_size: &str, ) -> Result { - u64::from_str_radix(numeric_string, radix) - .map_err(|_| ParseSizeError::ParseFailure(original_size.to_string())) + u64::from_str_radix(numeric_string, radix).map_err(|e| match e.kind() { + IntErrorKind::PosOverflow => ParseSizeError::size_too_big(original_size), + _ => ParseSizeError::ParseFailure(original_size.to_string()), + }) } } @@ -232,6 +235,23 @@ pub fn parse_size(size: &str) -> Result { Parser::default().parse(size) } +/// Same as `parse_size()`, except returns `u64::MAX` on overflow +/// GNU lib/coreutils include similar functionality +/// and GNU test suite checks this behavior for some utils +pub fn parse_size_max(size: &str) -> Result { + let result = Parser::default().parse(size); + match result { + Ok(_) => result, + Err(error) => { + if let ParseSizeError::SizeTooBig(_) = error { + Ok(u64::MAX) + } else { + Err(error) + } + } + } +} + #[derive(Debug, PartialEq, Eq)] pub enum ParseSizeError { InvalidSuffix(String), // Suffix @@ -392,6 +412,14 @@ mod tests { ); } + #[test] + #[cfg(not(target_pointer_width = "128"))] + fn overflow_to_max_x64() { + assert_eq!(Ok(u64::MAX), parse_size_max("18446744073709551616")); + assert_eq!(Ok(u64::MAX), parse_size_max("10000000000000000000000")); + assert_eq!(Ok(u64::MAX), parse_size_max("1Y")); + } + #[test] fn invalid_suffix() { let test_strings = ["5mib", "1eb", "1H"]; From 862a2df924eabf9c2c99dae520b1d4d78e361b57 Mon Sep 17 00:00:00 2001 From: Yury Zhytkou <54360928+zhitkoff@users.noreply.github.com> Date: Mon, 4 Sep 2023 12:23:49 -0400 Subject: [PATCH 0040/2851] build-gnu.sh: fix for /usr/bin/timeout on MacOS (#5194) * build-gnu.sh: `/usr/bin/timeout` should not be hardcoded to /usr/bin location Fixes #5193 --- util/build-gnu.sh | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/util/build-gnu.sh b/util/build-gnu.sh index a3dd0b9ad53..157265f61bf 100755 --- a/util/build-gnu.sh +++ b/util/build-gnu.sh @@ -18,10 +18,30 @@ path_GNU="$(readlink -fm -- "${path_GNU:-${path_UUTILS}/../gnu}")" ### +# On MacOS there is no system /usr/bin/timeout +# and trying to add it to /usr/bin (with symlink of copy binary) will fail unless system integrity protection is disabled (not ideal) +# ref: https://support.apple.com/en-us/102149 +# On MacOS the Homebrew coreutils could be installed and then "sudo ln -s /opt/homebrew/bin/timeout /usr/local/bin/timeout" +# Set to /usr/local/bin/timeout instead if /usr/bin/timeout is not found +SYSTEM_TIMEOUT="timeout" +if [ -x /usr/bin/timeout ] ; then + SYSTEM_TIMEOUT="/usr/bin/timeout" +elif [ -x /usr/local/bin/timeout ] ; then + SYSTEM_TIMEOUT="/usr/local/bin/timeout" +fi + +### + +release_tag_GNU="v9.4" + if test ! -d "${path_GNU}"; then echo "Could not find GNU coreutils (expected at '${path_GNU}')" echo "Run the following to download into the expected path:" echo "git clone --recurse-submodules https://github.com/coreutils/coreutils.git \"${path_GNU}\"" + echo "After downloading GNU coreutils to \"${path_GNU}\" run the following commands to checkout latest release tag" + echo "cd \"${path_GNU}\"" + echo "git fetch --all --tags" + echo "git checkout tags/${release_tag_GNU}" exit 1 fi @@ -82,7 +102,7 @@ else ./bootstrap --skip-po ./configure --quiet --disable-gcc-warnings #Add timeout to to protect against hangs - sed -i 's|^"\$@|/usr/bin/timeout 600 "\$@|' build-aux/test-driver + sed -i 's|^"\$@|'"${SYSTEM_TIMEOUT}"' 600 "\$@|' build-aux/test-driver # Change the PATH in the Makefile to test the uutils coreutils instead of the GNU coreutils sed -i "s/^[[:blank:]]*PATH=.*/ PATH='${UU_BUILD_DIR//\//\\/}\$(PATH_SEPARATOR)'\"\$\$PATH\" \\\/" Makefile sed -i 's| tr | /usr/bin/tr |' tests/init.sh @@ -153,13 +173,14 @@ sed -i 's|touch |/usr/bin/touch |' tests/cp/reflink-perm.sh tests/ls/block-size. sed -i 's|ln -|/usr/bin/ln -|' tests/cp/link-deref.sh sed -i 's|cp |/usr/bin/cp |' tests/mv/hard-2.sh sed -i 's|paste |/usr/bin/paste |' tests/od/od-endian.sh -sed -i 's|timeout |/usr/bin/timeout |' tests/tail/follow-stdin.sh +sed -i 's|timeout |'"${SYSTEM_TIMEOUT}"' |' tests/tail/follow-stdin.sh # Add specific timeout to tests that currently hang to limit time spent waiting -sed -i 's|\(^\s*\)seq \$|\1/usr/bin/timeout 0.1 seq \$|' tests/seq/seq-precision.sh tests/seq/seq-long-double.sh +sed -i 's|\(^\s*\)seq \$|\1'"${SYSTEM_TIMEOUT}"' 0.1 seq \$|' tests/seq/seq-precision.sh tests/seq/seq-long-double.sh -# Remove dup of /usr/bin/ when executed several times +# Remove dup of /usr/bin/ and /usr/local/bin/ when executed several times grep -rlE '/usr/bin/\s?/usr/bin' init.cfg tests/* | xargs --no-run-if-empty sed -Ei 's|/usr/bin/\s?/usr/bin/|/usr/bin/|g' +grep -rlE '/usr/local/bin/\s?/usr/local/bin' init.cfg tests/* | xargs --no-run-if-empty sed -Ei 's|/usr/local/bin/\s?/usr/local/bin/|/usr/local/bin/|g' #### Adjust tests to make them work with Rust/coreutils # in some cases, what we are doing in rust/coreutils is good (or better) From 7eec0ddaa364464f0567945934876cf8ac30fcbd Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 5 Sep 2023 05:06:00 +0000 Subject: [PATCH 0041/2851] chore(deps): update rust crate half to 2.3 --- Cargo.lock | 5 +++-- Cargo.toml | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c3e7ae92f2a..26a38fd3662 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1028,10 +1028,11 @@ checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[package]] name = "half" -version = "2.2.1" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02b4af3693f1b705df946e9fe5631932443781d0aabb423b62fcd4d73f6d2fd0" +checksum = "bc52e53916c08643f1b56ec082790d1e86a32e58dc5268f897f313fbae7b4872" dependencies = [ + "cfg-if", "crunchy", ] diff --git a/Cargo.toml b/Cargo.toml index 97edb75dd7a..9b92b342c66 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -284,7 +284,7 @@ fts-sys = "0.2" fundu = "2.0.0" gcd = "2.3" glob = "0.3.1" -half = "2.2" +half = "2.3" indicatif = "0.17" is-terminal = "0.4.9" itertools = "0.11.0" From f30b59efc0bf7d9fa49602058e7788b7edff47bf Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Tue, 5 Sep 2023 07:37:14 +0200 Subject: [PATCH 0042/2851] wc: use Rust's ilog10(), remove custom ilog10 fn --- src/uu/wc/src/wc.rs | 28 +--------------------------- 1 file changed, 1 insertion(+), 27 deletions(-) diff --git a/src/uu/wc/src/wc.rs b/src/uu/wc/src/wc.rs index 6d5894db058..663bbda151b 100644 --- a/src/uu/wc/src/wc.rs +++ b/src/uu/wc/src/wc.rs @@ -702,7 +702,7 @@ fn compute_number_width(inputs: &Inputs, settings: &Settings) -> usize { if total == 0 { minimum_width } else { - let total_width = (1 + ilog10_u64(total)) + let total_width = (1 + total.ilog10()) .try_into() .expect("ilog of a u64 should fit into a usize"); max(total_width, minimum_width) @@ -857,29 +857,3 @@ fn print_stats( writeln!(stdout) } } - -// TODO: remove and just use usize::ilog10 once the MSRV is >= 1.67. -fn ilog10_u64(mut u: u64) -> u32 { - if u == 0 { - panic!("cannot compute log of 0") - } - let mut log = 0; - if u >= 10_000_000_000 { - log += 10; - u /= 10_000_000_000; - } - if u >= 100_000 { - log += 5; - u /= 100_000; - } - // Rust's standard library in versions >= 1.67 does something even more clever than this, but - // this should work just fine for the time being. - log + match u { - 1..=9 => 0, - 10..=99 => 1, - 100..=999 => 2, - 1000..=9999 => 3, - 10000..=99999 => 4, - _ => unreachable!(), - } -} From e493b9c5275e8764d33bb464bf0be6e977b531f5 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Tue, 5 Sep 2023 08:16:25 +0200 Subject: [PATCH 0043/2851] yes: use let/else to fix todo --- src/uu/yes/src/yes.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/uu/yes/src/yes.rs b/src/uu/yes/src/yes.rs index e6868d9d85e..a58b734045b 100644 --- a/src/uu/yes/src/yes.rs +++ b/src/uu/yes/src/yes.rs @@ -58,10 +58,7 @@ fn args_into_buffer<'a>( buf: &mut Vec, i: Option>, ) -> Result<(), Box> { - // TODO: this should be replaced with let/else once available in the MSRV. - let i = if let Some(i) = i { - i - } else { + let Some(i) = i else { buf.extend_from_slice(b"y\n"); return Ok(()); }; From 862a63835048d0e9d534a4ea1e33f08fb016a176 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Tue, 5 Sep 2023 09:44:30 +0200 Subject: [PATCH 0044/2851] Remove is-terminal from Cargo.lock --- Cargo.lock | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 28ae46ccdb3..25377dc0cd6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1136,17 +1136,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "is-terminal" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" -dependencies = [ - "hermit-abi", - "rustix 0.38.8", - "windows-sys 0.48.0", -] - [[package]] name = "itertools" version = "0.11.0" From 1a086ead7f69526d70447c9450ad709f2175fa89 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Tue, 5 Sep 2023 10:05:58 +0200 Subject: [PATCH 0045/2851] build-gnu.sh: fix formatting issues --- util/build-gnu.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/util/build-gnu.sh b/util/build-gnu.sh index 157265f61bf..2d949bbb333 100755 --- a/util/build-gnu.sh +++ b/util/build-gnu.sh @@ -18,16 +18,16 @@ path_GNU="$(readlink -fm -- "${path_GNU:-${path_UUTILS}/../gnu}")" ### -# On MacOS there is no system /usr/bin/timeout +# On MacOS there is no system /usr/bin/timeout # and trying to add it to /usr/bin (with symlink of copy binary) will fail unless system integrity protection is disabled (not ideal) # ref: https://support.apple.com/en-us/102149 # On MacOS the Homebrew coreutils could be installed and then "sudo ln -s /opt/homebrew/bin/timeout /usr/local/bin/timeout" # Set to /usr/local/bin/timeout instead if /usr/bin/timeout is not found SYSTEM_TIMEOUT="timeout" -if [ -x /usr/bin/timeout ] ; then +if [ -x /usr/bin/timeout ]; then SYSTEM_TIMEOUT="/usr/bin/timeout" -elif [ -x /usr/local/bin/timeout ] ; then - SYSTEM_TIMEOUT="/usr/local/bin/timeout" +elif [ -x /usr/local/bin/timeout ]; then + SYSTEM_TIMEOUT="/usr/local/bin/timeout" fi ### From cefb4eb265b6ae13d57c003e9b9c636b76786b86 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Tue, 5 Sep 2023 10:43:26 +0200 Subject: [PATCH 0046/2851] Bump blake3 from 1.4.0 to 1.4.1 --- Cargo.lock | 14 ++++++++++---- Cargo.toml | 2 +- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 25377dc0cd6..86c37482800 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -161,20 +161,20 @@ checksum = "3c2f0dc9a68c6317d884f97cc36cf5a3d20ba14ce404227df55e1af708ab04bc" dependencies = [ "arrayref", "arrayvec", - "constant_time_eq", + "constant_time_eq 0.2.4", ] [[package]] name = "blake3" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "729b71f35bd3fa1a4c86b85d32c8b9069ea7fe14f7a53cfabb65f62d4265b888" +checksum = "199c42ab6972d92c9f8995f086273d25c42fc0f7b2a1fcefba465c1352d25ba5" dependencies = [ "arrayref", "arrayvec", "cc", "cfg-if", - "constant_time_eq", + "constant_time_eq 0.3.0", "digest", ] @@ -360,6 +360,12 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3ad85c1f65dc7b37604eb0e89748faf0b9653065f2a8ef69f96a687ec1e9279" +[[package]] +name = "constant_time_eq" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2" + [[package]] name = "conv" version = "0.3.3" diff --git a/Cargo.toml b/Cargo.toml index 5e2b4230a1a..51b64aff898 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -339,7 +339,7 @@ sha1 = "0.10.5" sha2 = "0.10.7" sha3 = "0.10.8" blake2b_simd = "1.0.1" -blake3 = "1.4.0" +blake3 = "1.4.1" sm3 = "0.4.2" digest = "0.10.7" From 442e468efcda70a2bac6f18bc642a513bec2293e Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Tue, 5 Sep 2023 10:45:14 +0200 Subject: [PATCH 0047/2851] Bump constant_time_eq from 0.2.4 to 0.2.6 --- Cargo.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 86c37482800..7b476f4477f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -161,7 +161,7 @@ checksum = "3c2f0dc9a68c6317d884f97cc36cf5a3d20ba14ce404227df55e1af708ab04bc" dependencies = [ "arrayref", "arrayvec", - "constant_time_eq 0.2.4", + "constant_time_eq 0.2.6", ] [[package]] @@ -356,9 +356,9 @@ dependencies = [ [[package]] name = "constant_time_eq" -version = "0.2.4" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3ad85c1f65dc7b37604eb0e89748faf0b9653065f2a8ef69f96a687ec1e9279" +checksum = "21a53c0a4d288377e7415b53dcfc3c04da5cdc2cc95c8d5ac178b58f0b861ad6" [[package]] name = "constant_time_eq" From dd584b6332967ba76acafec983dbba2189af32f3 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Tue, 5 Sep 2023 10:49:32 +0200 Subject: [PATCH 0048/2851] deny.toml: add constant_time_eq to skip list --- deny.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/deny.toml b/deny.toml index c2e580a4de0..678fc352dc3 100644 --- a/deny.toml +++ b/deny.toml @@ -87,6 +87,8 @@ skip = [ { name = "syn", version = "1.0.109" }, # various crates { name = "bitflags", version = "1.3.2" }, + # blake2b_simd + { name = "constant_time_eq", version = "0.2.6" }, ] # spell-checker: enable From 3cce11b46ff2229fffc39f84237007bc86300ede Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 5 Sep 2023 14:17:59 +0000 Subject: [PATCH 0049/2851] chore(deps): update rust crate walkdir to 2.4 --- Cargo.lock | 5 ++--- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 25377dc0cd6..eb1cdd4b174 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3311,12 +3311,11 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "walkdir" -version = "2.3.2" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" +checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" dependencies = [ "same-file", - "winapi", "winapi-util", ] diff --git a/Cargo.toml b/Cargo.toml index 5e2b4230a1a..dbe5bd10e2a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -327,7 +327,7 @@ time = { version = "0.3" } unicode-segmentation = "1.10.1" unicode-width = "0.1.10" utf-8 = "0.7.6" -walkdir = "2.3" +walkdir = "2.4" winapi-util = "0.1.5" windows-sys = { version = "0.48.0", default-features = false } xattr = "1.0.1" From 2ae1d8d1ccd7c4fff24b51b8d914cda2be9de006 Mon Sep 17 00:00:00 2001 From: zhitkoff Date: Tue, 5 Sep 2023 17:13:30 -0400 Subject: [PATCH 0050/2851] split: missing functionality for --number option --- src/uu/split/src/split.rs | 218 ++++++++++++++++++++++-- src/uucore/src/lib/parser/parse_size.rs | 2 +- 2 files changed, 204 insertions(+), 16 deletions(-) diff --git a/src/uu/split/src/split.rs b/src/uu/split/src/split.rs index e39f6e93ffe..23520f7092b 100644 --- a/src/uu/split/src/split.rs +++ b/src/uu/split/src/split.rs @@ -19,9 +19,10 @@ use std::fs::{metadata, File}; use std::io; use std::io::{stdin, BufRead, BufReader, BufWriter, ErrorKind, Read, Write}; use std::path::Path; +use std::u64; use uucore::display::Quotable; use uucore::error::{FromIo, UIoError, UResult, USimpleError, UUsageError}; -use uucore::parse_size::{parse_size_max, ParseSizeError}; +use uucore::parse_size::{parse_size, parse_size_max, ParseSizeError}; use uucore::uio_error; use uucore::{format_usage, help_about, help_section, help_usage}; @@ -337,6 +338,10 @@ enum NumberType { /// Split into a specific number of chunks by byte. Bytes(u64), + /// Split into a specific number of chunks by byte + /// but output only the *k*th chunk. + KthBytes(u64, u64), + /// Split into a specific number of chunks by line (approximately). Lines(u64), @@ -349,7 +354,7 @@ enum NumberType { /// Assign lines via round-robin to the specified number of output /// chunks, but output only the *k*th chunk. - KthRoundRobin(u64, u64), + KthRoundRobin(u64, u64), // not yet implemented? } impl NumberType { @@ -357,6 +362,7 @@ impl NumberType { fn num_chunks(&self) -> u64 { match self { Self::Bytes(n) => *n, + Self::KthBytes(_, n) => *n, Self::Lines(n) => *n, Self::KthLines(_, n) => *n, Self::RoundRobin(n) => *n, @@ -375,6 +381,7 @@ enum NumberTypeError { /// /// ```ignore /// -n N + /// -n K/N /// -n l/N /// -n l/K/N /// -n r/N @@ -385,9 +392,12 @@ enum NumberTypeError { /// The chunk number was invalid. /// /// This can happen if the value of `K` in any of the following - /// command-line options is not a positive integer: + /// command-line options is not a positive integer + /// or if `K` is 0 + /// or if `K` is greater than `N`: /// /// ```ignore + /// -n K/N /// -n l/K/N /// -n r/K/N /// ``` @@ -401,6 +411,7 @@ impl NumberType { /// /// ```ignore /// "N" + /// "K/N" /// "l/N" /// "l/K/N" /// "r/N" @@ -412,14 +423,17 @@ impl NumberType { /// /// # Errors /// - /// If the string is not one of the valid number types, if `K` is - /// not a nonnegative integer, or if `N` is not a positive - /// integer, then this function returns [`NumberTypeError`]. + /// If the string is not one of the valid number types, + /// if `K` is not a nonnegative integer, + /// or if `K` is 0, + /// or if `N` is not a positive integer, + /// or if `K` is greater than `N` + /// then this function returns [`NumberTypeError`]. fn from(s: &str) -> Result { let parts: Vec<&str> = s.split('/').collect(); match &parts[..] { [n_str] => { - let num_chunks = parse_size_max(n_str) + let num_chunks = parse_size(n_str) .map_err(|_| NumberTypeError::NumberOfChunks(n_str.to_string()))?; if num_chunks > 0 { Ok(Self::Bytes(num_chunks)) @@ -427,28 +441,44 @@ impl NumberType { Err(NumberTypeError::NumberOfChunks(s.to_string())) } } + [k_str, n_str] if !k_str.starts_with('l') && !k_str.starts_with('r') => { + let num_chunks = parse_size(n_str) + .map_err(|_| NumberTypeError::NumberOfChunks(n_str.to_string()))?; + let chunk_number = parse_size(k_str) + .map_err(|_| NumberTypeError::ChunkNumber(k_str.to_string()))?; + if chunk_number > num_chunks || chunk_number == 0 { + return Err(NumberTypeError::ChunkNumber(k_str.to_string())); + } + Ok(Self::KthBytes(chunk_number, num_chunks)) + } ["l", n_str] => { - let num_chunks = parse_size_max(n_str) + let num_chunks = parse_size(n_str) .map_err(|_| NumberTypeError::NumberOfChunks(n_str.to_string()))?; Ok(Self::Lines(num_chunks)) } ["l", k_str, n_str] => { - let num_chunks = parse_size_max(n_str) + let num_chunks = parse_size(n_str) .map_err(|_| NumberTypeError::NumberOfChunks(n_str.to_string()))?; - let chunk_number = parse_size_max(k_str) + let chunk_number = parse_size(k_str) .map_err(|_| NumberTypeError::ChunkNumber(k_str.to_string()))?; + if chunk_number > num_chunks || chunk_number == 0 { + return Err(NumberTypeError::ChunkNumber(k_str.to_string())); + } Ok(Self::KthLines(chunk_number, num_chunks)) } ["r", n_str] => { - let num_chunks = parse_size_max(n_str) + let num_chunks = parse_size(n_str) .map_err(|_| NumberTypeError::NumberOfChunks(n_str.to_string()))?; Ok(Self::RoundRobin(num_chunks)) } ["r", k_str, n_str] => { - let num_chunks = parse_size_max(n_str) + let num_chunks = parse_size(n_str) .map_err(|_| NumberTypeError::NumberOfChunks(n_str.to_string()))?; - let chunk_number = parse_size_max(k_str) + let chunk_number = parse_size(k_str) .map_err(|_| NumberTypeError::ChunkNumber(k_str.to_string()))?; + if chunk_number > num_chunks || chunk_number == 0 { + return Err(NumberTypeError::ChunkNumber(k_str.to_string())); + } Ok(Self::KthRoundRobin(chunk_number, num_chunks)) } _ => Err(NumberTypeError::NumberOfChunks(s.to_string())), @@ -538,7 +568,13 @@ impl Strategy { let v = parse_size_max(v).map_err(|_| { StrategyError::Lines(ParseSizeError::ParseFailure(v.to_string())) })?; - Ok(Self::Lines(v)) + if v > 0 { + Ok(Self::Lines(v)) + } else { + Err(StrategyError::Lines(ParseSizeError::ParseFailure( + v.to_string(), + ))) + } } (None, false, false, false, false) => Ok(Self::Lines(1000)), (None, true, false, false, false) => { @@ -1263,6 +1299,93 @@ where } } +/// Print the k-th chunk of a file to stdout, splitting by byte. +/// +/// This function is like [`split_into_n_chunks_by_byte`], but instead +/// of writing each chunk to its own file, it only writes to stdout +/// the contents of the chunk identified by `chunk_number` +/// +/// # Errors +/// +/// This function returns an error if there is a problem reading from +/// `reader` or writing to stdout. +fn kth_chunks_by_byte( + settings: &Settings, + reader: &mut R, + chunk_number: u64, + num_chunks: u64, +) -> UResult<()> +where + R: BufRead, +{ + // Get the size of the input file in bytes and compute the number + // of bytes per chunk. + // + // If the requested number of chunks exceeds the number of bytes + // in the file - just write empty byte string to stdout + // NOTE: the `elide_empty_files` parameter is ignored here + // as we do not generate any files + // and instead writing to stdout + let metadata = metadata(&settings.input).map_err(|_| { + USimpleError::new(1, format!("{}: cannot determine file size", settings.input)) + })?; + + let num_bytes = metadata.len(); + // If input file is empty and we would have written zero chunks of output, + // then terminate immediately. + // This happens on `split -e -n 3 /dev/null`, for example. + if num_bytes == 0 { + return Ok(()); + } + + // Write to stdout instead of to a file. + let stdout = std::io::stdout(); + let mut writer = stdout.lock(); + + let chunk_size = (num_bytes / (num_chunks)).max(1); + let mut num_bytes: usize = num_bytes.try_into().unwrap(); + + let mut i = 1; + loop { + let buf: &mut Vec = &mut vec![]; + if num_bytes > 0 { + // Read `chunk_size` bytes from the reader into `buf` + // except the last. + // + // The last chunk gets all remaining bytes so that if the number + // of bytes in the input file was not evenly divisible by + // `num_chunks`, we don't leave any bytes behind. + let limit = { + if i == num_chunks { + num_bytes.try_into().unwrap() + } else { + chunk_size + } + }; + let n_bytes_read = reader.by_ref().take(limit).read_to_end(buf); + match n_bytes_read { + Ok(n_bytes) => { + num_bytes -= n_bytes; + } + Err(error) => { + return Err(USimpleError::new( + 1, + format!("{}: cannot read from input : {}", settings.input, error), + )); + } + } + if i == chunk_number { + writer.write_all(buf)?; + break; + } + i += 1; + } else { + break; + } + } + Ok(()) +} + /// Split a file into a specific number of chunks by line. /// /// This function always creates one output file for each chunk, even @@ -1438,6 +1561,50 @@ where Ok(()) } +/// Print the k-th chunk of a file, splitting by line, but +/// assign lines via round-robin to the specified number of output +/// chunks, but output only the *k*th chunk. +/// +/// This function is like [`kth_chunk_by_line`], as it only writes to stdout and +/// prints out only *k*th chunk +/// It is also like [`split_into_n_chunks_by_line_round_robin`], as it is assigning chunks +/// using round robin distribution +/// +/// # Errors +/// +/// This function returns an error if there is a problem reading from +/// `reader` or writing to one of the output files. +/// +/// # See also +/// +/// * [`split_into_n_chunks_by_line_round_robin`], which splits its input in the +/// same way, but writes each chunk to its own file. +fn kth_chunk_by_line_round_robin( + _settings: &Settings, + reader: &mut R, + chunk_number: u64, + num_chunks: u64, +) -> UResult<()> +where + R: BufRead, +{ + // Write to stdout instead of to a file. + let stdout = std::io::stdout(); + let mut writer = stdout.lock(); + + let num_chunks: usize = num_chunks.try_into().unwrap(); + let chunk_number: usize = chunk_number.try_into().unwrap(); + for (i, line_result) in reader.lines().enumerate() { + let line = line_result?; + let bytes = line.as_bytes(); + if (i % num_chunks) == chunk_number { + writer.write_all(bytes)?; + writer.write_all(b"\n")?; + } + } + Ok(()) +} + fn split(settings: &Settings) -> UResult<()> { let mut reader = BufReader::new(if settings.input == "-" { Box::new(stdin()) as Box @@ -1455,6 +1622,9 @@ fn split(settings: &Settings) -> UResult<()> { Strategy::Number(NumberType::Bytes(num_chunks)) => { split_into_n_chunks_by_byte(settings, &mut reader, num_chunks) } + Strategy::Number(NumberType::KthBytes(chunk_number, num_chunks)) => { + kth_chunks_by_byte(settings, &mut reader, chunk_number, num_chunks) + } Strategy::Number(NumberType::Lines(num_chunks)) => { split_into_n_chunks_by_line(settings, &mut reader, num_chunks) } @@ -1467,7 +1637,12 @@ fn split(settings: &Settings) -> UResult<()> { Strategy::Number(NumberType::RoundRobin(num_chunks)) => { split_into_n_chunks_by_line_round_robin(settings, &mut reader, num_chunks) } - Strategy::Number(_) => Err(USimpleError::new(1, "-n mode not yet fully implemented")), + Strategy::Number(NumberType::KthRoundRobin(chunk_number, num_chunks)) => { + // The chunk number is given as a 1-indexed number, but it + // is a little easier to deal with a 0-indexed number. + let chunk_number = chunk_number - 1; + kth_chunk_by_line_round_robin(settings, &mut reader, chunk_number, num_chunks) + } Strategy::Lines(chunk_size) => { let mut writer = LineChunkWriter::new(chunk_size, settings)?; match std::io::copy(&mut reader, &mut writer) { @@ -1570,6 +1745,18 @@ mod tests { NumberType::from("l/abc/456").unwrap_err(), NumberTypeError::ChunkNumber("abc".to_string()) ); + assert_eq!( + NumberType::from("l/456/123").unwrap_err(), + NumberTypeError::ChunkNumber("456".to_string()) + ); + assert_eq!( + NumberType::from("r/456/123").unwrap_err(), + NumberTypeError::ChunkNumber("456".to_string()) + ); + assert_eq!( + NumberType::from("456/123").unwrap_err(), + NumberTypeError::ChunkNumber("456".to_string()) + ); // In GNU split, the number of chunks get precedence: // // $ split -n l/abc/xyz @@ -1605,6 +1792,7 @@ mod tests { #[test] fn test_number_type_num_chunks() { assert_eq!(NumberType::from("123").unwrap().num_chunks(), 123); + assert_eq!(NumberType::from("123/456").unwrap().num_chunks(), 456); assert_eq!(NumberType::from("l/123").unwrap().num_chunks(), 123); assert_eq!(NumberType::from("l/123/456").unwrap().num_chunks(), 456); assert_eq!(NumberType::from("r/123").unwrap().num_chunks(), 123); diff --git a/src/uucore/src/lib/parser/parse_size.rs b/src/uucore/src/lib/parser/parse_size.rs index 63039e6093b..4d9968bb737 100644 --- a/src/uucore/src/lib/parser/parse_size.rs +++ b/src/uucore/src/lib/parser/parse_size.rs @@ -236,7 +236,7 @@ pub fn parse_size(size: &str) -> Result { } /// Same as `parse_size()`, except returns `u64::MAX` on overflow -/// GNU lib/coreutils include similar functionality +/// GNU lib/coreutils include similar functionality /// and GNU test suite checks this behavior for some utils pub fn parse_size_max(size: &str) -> Result { let result = Parser::default().parse(size); From 8be6338da9f87a49589752942186d328884c0d1a Mon Sep 17 00:00:00 2001 From: David Matos Date: Tue, 5 Sep 2023 23:35:14 +0200 Subject: [PATCH 0051/2851] cp: Error out if cp only contains source --- src/uu/cp/src/cp.rs | 3 +++ tests/by-util/test_cp.rs | 12 ++++++++++++ 2 files changed, 15 insertions(+) diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index 7dbf8aedb50..516f20fdad5 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -1073,6 +1073,9 @@ fn parse_path_args( if paths.is_empty() { // No files specified return Err("missing file operand".into()); + } else if paths.len() == 1 && options.target_dir.is_none() { + // Only one file specified + return Err(format!("missing destination file operand after {:?}", paths[0]).into()); } // Return an error if the user requested to copy more than one diff --git a/tests/by-util/test_cp.rs b/tests/by-util/test_cp.rs index f72316b5daa..c77be3d4ed9 100644 --- a/tests/by-util/test_cp.rs +++ b/tests/by-util/test_cp.rs @@ -3231,3 +3231,15 @@ fn test_cp_debug_sparse_always_reflink_auto() { panic!("Failure: stdout was \n{stdout_str}"); } } + +#[test] +fn test_cp_only_source_no_target() { + let ts = TestScenario::new(util_name!()); + let at = &ts.fixtures; + at.touch("a"); + let result = ts.ucmd().arg("a").fails(); + let stderr_str = result.stderr_str(); + if !stderr_str.contains("missing destination file operand after \"a\"") { + panic!("Failure: stderr was \n{stderr_str}"); + } +} From 2a0b4f88370f1ad787937d174b0ca8e6c2e4bf10 Mon Sep 17 00:00:00 2001 From: Yury Zhytkou <54360928+zhitkoff@users.noreply.github.com> Date: Tue, 5 Sep 2023 18:14:14 -0400 Subject: [PATCH 0052/2851] Update build-gnu.sh --- util/build-gnu.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/util/build-gnu.sh b/util/build-gnu.sh index b57b4fe769e..2d949bbb333 100755 --- a/util/build-gnu.sh +++ b/util/build-gnu.sh @@ -19,7 +19,6 @@ path_GNU="$(readlink -fm -- "${path_GNU:-${path_UUTILS}/../gnu}")" ### # On MacOS there is no system /usr/bin/timeout - # and trying to add it to /usr/bin (with symlink of copy binary) will fail unless system integrity protection is disabled (not ideal) # ref: https://support.apple.com/en-us/102149 # On MacOS the Homebrew coreutils could be installed and then "sudo ln -s /opt/homebrew/bin/timeout /usr/local/bin/timeout" From a0a9ee6491ca3402e9fdc3af5b7b3f5def3b8268 Mon Sep 17 00:00:00 2001 From: zhitkoff Date: Tue, 5 Sep 2023 18:38:22 -0400 Subject: [PATCH 0053/2851] split: fixing tests for parse_size_max() --- tests/by-util/test_split.rs | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/tests/by-util/test_split.rs b/tests/by-util/test_split.rs index 053b6f8bf19..2d6474511fa 100644 --- a/tests/by-util/test_split.rs +++ b/tests/by-util/test_split.rs @@ -608,14 +608,6 @@ fn test_split_invalid_bytes_size() { .fails() .code_is(1) .stderr_only("split: invalid number of bytes: '1024R'\n"); - #[cfg(not(target_pointer_width = "128"))] - new_ucmd!() - .args(&["-b", "1Y"]) - .fails() - .code_is(1) - .stderr_only( - "split: invalid number of bytes: '1Y': Value too large for defined data type\n", - ); #[cfg(target_pointer_width = "32")] { let sizes = ["1000G", "10T"]; @@ -625,6 +617,18 @@ fn test_split_invalid_bytes_size() { } } +#[test] +fn test_split_overflow_bytes_size() { + #[cfg(not(target_pointer_width = "128"))] + let (at, mut ucmd) = at_and_ucmd!(); + let name = "test_split_overflow_bytes_size"; + RandomFile::new(&at, name).add_bytes(1000); + ucmd.args(&["-b", "1Y", name]).succeeds(); + let glob = Glob::new(&at, ".", r"x[[:alpha:]][[:alpha:]]$"); + assert_eq!(glob.count(), 1); + assert_eq!(glob.collate(), at.read_bytes(name)); +} + #[test] fn test_split_chunks_num_chunks_oversized_32() { #[cfg(target_pointer_width = "32")] From eaae32ec3bfbf842933b706001368314ee221851 Mon Sep 17 00:00:00 2001 From: zhitkoff Date: Tue, 5 Sep 2023 20:12:25 -0400 Subject: [PATCH 0054/2851] split: comments --- src/uu/split/src/split.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/split/src/split.rs b/src/uu/split/src/split.rs index 23520f7092b..6fb05dabb5f 100644 --- a/src/uu/split/src/split.rs +++ b/src/uu/split/src/split.rs @@ -354,7 +354,7 @@ enum NumberType { /// Assign lines via round-robin to the specified number of output /// chunks, but output only the *k*th chunk. - KthRoundRobin(u64, u64), // not yet implemented? + KthRoundRobin(u64, u64), } impl NumberType { From 5c93c592db69312c0460c66c78fdddb624fc7462 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Wed, 6 Sep 2023 09:47:50 +0200 Subject: [PATCH 0055/2851] uucore: turn version_cmp into a feature --- src/uu/ls/Cargo.toml | 2 +- src/uu/sort/Cargo.toml | 2 +- src/uucore/Cargo.toml | 1 + src/uucore/src/lib/features.rs | 2 ++ src/uucore/src/lib/{mods => features}/version_cmp.rs | 0 src/uucore/src/lib/lib.rs | 3 ++- src/uucore/src/lib/mods.rs | 1 - 7 files changed, 7 insertions(+), 4 deletions(-) rename src/uucore/src/lib/{mods => features}/version_cmp.rs (100%) diff --git a/src/uu/ls/Cargo.toml b/src/uu/ls/Cargo.toml index f3fc2eb6bfd..24b6947fd53 100644 --- a/src/uu/ls/Cargo.toml +++ b/src/uu/ls/Cargo.toml @@ -23,7 +23,7 @@ term_grid = { workspace = true } terminal_size = { workspace = true } glob = { workspace = true } lscolors = { workspace = true } -uucore = { workspace = true, features = ["entries", "fs"] } +uucore = { workspace = true, features = ["entries", "fs", "version-cmp"] } once_cell = { workspace = true } selinux = { workspace = true, optional = true } diff --git a/src/uu/sort/Cargo.toml b/src/uu/sort/Cargo.toml index 359a2b01274..981e7164144 100644 --- a/src/uu/sort/Cargo.toml +++ b/src/uu/sort/Cargo.toml @@ -27,7 +27,7 @@ rayon = { workspace = true } self_cell = { workspace = true } tempfile = { workspace = true } unicode-width = { workspace = true } -uucore = { workspace = true, features = ["fs"] } +uucore = { workspace = true, features = ["fs", "version-cmp"] } [[bin]] name = "sort" diff --git a/src/uucore/Cargo.toml b/src/uucore/Cargo.toml index fcbfdeac757..15a053ac4cd 100644 --- a/src/uucore/Cargo.toml +++ b/src/uucore/Cargo.toml @@ -100,4 +100,5 @@ sum = [ update-control = [] utf8 = [] utmpx = ["time", "time/macros", "libc", "dns-lookup"] +version-cmp = [] wide = [] diff --git a/src/uucore/src/lib/features.rs b/src/uucore/src/lib/features.rs index 786a6468239..49eee38439b 100644 --- a/src/uucore/src/lib/features.rs +++ b/src/uucore/src/lib/features.rs @@ -26,6 +26,8 @@ pub mod sum; mod tokenize; #[cfg(feature = "update-control")] pub mod update_control; +#[cfg(feature = "version-cmp")] +pub mod version_cmp; // * (platform-specific) feature-gated modules // ** non-windows (i.e. Unix + Fuchsia) diff --git a/src/uucore/src/lib/mods/version_cmp.rs b/src/uucore/src/lib/features/version_cmp.rs similarity index 100% rename from src/uucore/src/lib/mods/version_cmp.rs rename to src/uucore/src/lib/features/version_cmp.rs diff --git a/src/uucore/src/lib/lib.rs b/src/uucore/src/lib/lib.rs index 5b9f4ae1266..c0732f069a8 100644 --- a/src/uucore/src/lib/lib.rs +++ b/src/uucore/src/lib/lib.rs @@ -26,7 +26,6 @@ pub use crate::mods::line_ending; pub use crate::mods::os; pub use crate::mods::panic; pub use crate::mods::quoting_style; -pub use crate::mods::version_cmp; // * string parsing modules pub use crate::parser::parse_glob; @@ -55,6 +54,8 @@ pub use crate::features::ringbuffer; pub use crate::features::sum; #[cfg(feature = "update-control")] pub use crate::features::update_control; +#[cfg(feature = "version-cmp")] +pub use crate::features::version_cmp; // * (platform-specific) feature-gated modules // ** non-windows (i.e. Unix + Fuchsia) diff --git a/src/uucore/src/lib/mods.rs b/src/uucore/src/lib/mods.rs index caa8fcb9290..d3a2dc19e8b 100644 --- a/src/uucore/src/lib/mods.rs +++ b/src/uucore/src/lib/mods.rs @@ -9,6 +9,5 @@ pub mod error; pub mod line_ending; pub mod os; pub mod panic; -pub mod version_cmp; // dir and vdir also need access to the quoting_style module pub mod quoting_style; From d8a16a235149ab60099c93c889437314095426fb Mon Sep 17 00:00:00 2001 From: zhitkoff Date: Wed, 6 Sep 2023 12:42:49 -0400 Subject: [PATCH 0056/2851] split: tests --- tests/by-util/test_split.rs | 99 ++++++++++++++++++++++++++++++++++++- 1 file changed, 98 insertions(+), 1 deletion(-) diff --git a/tests/by-util/test_split.rs b/tests/by-util/test_split.rs index 2d6474511fa..da4a4475bec 100644 --- a/tests/by-util/test_split.rs +++ b/tests/by-util/test_split.rs @@ -377,6 +377,22 @@ fn test_split_obs_lines_standalone() { let glob = Glob::new(&at, ".", r"x[[:alpha:]][[:alpha:]]$"); assert_eq!(glob.count(), 2); assert_eq!(glob.collate(), at.read_bytes(name)); + ucmd.args(&["-99999999999999999991", name]).succeeds().no_stderr().no_stdout(); + let glob = Glob::new(&at, ".", r"x[[:alpha:]][[:alpha:]]$"); + assert_eq!(glob.count(), 2); + assert_eq!(glob.collate(), at.read_bytes(name)); +} + +/// Test for obsolete lines option standalone overflow +#[test] +fn test_split_obs_lines_standalone_overflow() { + let (at, mut ucmd) = at_and_ucmd!(); + let name = "obs-lines-standalone"; + RandomFile::new(&at, name).add_lines(4); + ucmd.args(&["-99999999999999999991", name]).succeeds().no_stderr().no_stdout(); + let glob = Glob::new(&at, ".", r"x[[:alpha:]][[:alpha:]]$"); + assert_eq!(glob.count(), 1); + assert_eq!(glob.collate(), at.read_bytes(name)); } /// Test for obsolete lines option as part of invalid combined short options @@ -756,7 +772,7 @@ creating file 'xaf' } #[test] -fn test_number() { +fn test_number_n() { let (at, mut ucmd) = at_and_ucmd!(); let file_read = |f| { let mut s = String::new(); @@ -771,6 +787,80 @@ fn test_number() { assert_eq!(file_read("xae"), "uvwxyz\n"); } +#[test] +fn test_number_kth_of_n() { + new_ucmd!() + .args(&["--number=3/5", "asciilowercase.txt"]) + .succeeds() + .stdout_only("klmno"); + new_ucmd!() + .args(&["-e", "--number=99/100", "asciilowercase.txt"]) + .succeeds() + .stdout_only(""); + new_ucmd!() + .args(&[ + "--number=r/9223372036854775807/18446744073709551615", + "asciilowercase.txt", + ]) + .succeeds() + .stdout_only(""); + new_ucmd!() + .args(&["--number=0/5", "asciilowercase.txt"]) + .fails() + .stderr_contains("split: invalid chunk number: 0"); + new_ucmd!() + .args(&["--number=10/5", "asciilowercase.txt"]) + .fails() + .stderr_contains("split: invalid chunk number: 10"); + new_ucmd!() + .args(&[ + "--number=9223372036854775807/18446744073709551616", + "asciilowercase.txt", + ]) + .fails() + .stderr_contains("split: invalid number of chunks: 18446744073709551616"); +} + +#[test] +fn test_number_kth_of_n_round_robin() { + new_ucmd!() + .args(&["--number", "r/2/3", "fivelines.txt"]) + .succeeds() + .stdout_only("2\n5\n"); + new_ucmd!() + .args(&["--number", "r/1/4", "fivelines.txt"]) + .succeeds() + .stdout_only("1\n5\n"); + new_ucmd!() + .args(&["-e", "--number", "r/7/7", "fivelines.txt"]) + .succeeds() + .stdout_only(""); + new_ucmd!() + .args(&[ + "--number", + "r/9223372036854775807/18446744073709551615", + "fivelines.txt", + ]) + .succeeds() + .stdout_only(""); + new_ucmd!() + .args(&[ + "--number", + "r/9223372036854775807/18446744073709551616", + "fivelines.txt", + ]) + .fails() + .stderr_contains("split: invalid number of chunks: 18446744073709551616"); + new_ucmd!() + .args(&["--number", "r/0/3", "fivelines.txt"]) + .fails() + .stderr_contains("split: invalid chunk number: 0"); + new_ucmd!() + .args(&["--number", "r/10/3", "fivelines.txt"]) + .fails() + .stderr_contains("split: invalid chunk number: 10"); +} + #[test] fn test_split_number_with_io_blksize() { let (at, mut ucmd) = at_and_ucmd!(); @@ -927,6 +1017,13 @@ fn test_line_bytes() { assert_eq!(at.read("xad"), "ee\n"); } +#[test] +fn test_line_bytes_overflow() { + let (at, mut ucmd) = at_and_ucmd!(); + ucmd.args(&["-C", "18446744073709551616", "letters.txt"]).succeeds(); + assert_eq!(at.read("xaa"), "aaaaaaaaa\nbbbb\ncccc\ndd\nee\n"); +} + #[test] fn test_line_bytes_concatenated_with_value() { let (at, mut ucmd) = at_and_ucmd!(); From e378454a265b103b98e940d5ed9b691985433f54 Mon Sep 17 00:00:00 2001 From: zhitkoff Date: Wed, 6 Sep 2023 13:15:35 -0400 Subject: [PATCH 0057/2851] split: formatting --- tests/by-util/test_split.rs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/tests/by-util/test_split.rs b/tests/by-util/test_split.rs index da4a4475bec..3ea74606cae 100644 --- a/tests/by-util/test_split.rs +++ b/tests/by-util/test_split.rs @@ -377,7 +377,10 @@ fn test_split_obs_lines_standalone() { let glob = Glob::new(&at, ".", r"x[[:alpha:]][[:alpha:]]$"); assert_eq!(glob.count(), 2); assert_eq!(glob.collate(), at.read_bytes(name)); - ucmd.args(&["-99999999999999999991", name]).succeeds().no_stderr().no_stdout(); + ucmd.args(&["-99999999999999999991", name]) + .succeeds() + .no_stderr() + .no_stdout(); let glob = Glob::new(&at, ".", r"x[[:alpha:]][[:alpha:]]$"); assert_eq!(glob.count(), 2); assert_eq!(glob.collate(), at.read_bytes(name)); @@ -389,7 +392,10 @@ fn test_split_obs_lines_standalone_overflow() { let (at, mut ucmd) = at_and_ucmd!(); let name = "obs-lines-standalone"; RandomFile::new(&at, name).add_lines(4); - ucmd.args(&["-99999999999999999991", name]).succeeds().no_stderr().no_stdout(); + ucmd.args(&["-99999999999999999991", name]) + .succeeds() + .no_stderr() + .no_stdout(); let glob = Glob::new(&at, ".", r"x[[:alpha:]][[:alpha:]]$"); assert_eq!(glob.count(), 1); assert_eq!(glob.collate(), at.read_bytes(name)); @@ -1020,7 +1026,8 @@ fn test_line_bytes() { #[test] fn test_line_bytes_overflow() { let (at, mut ucmd) = at_and_ucmd!(); - ucmd.args(&["-C", "18446744073709551616", "letters.txt"]).succeeds(); + ucmd.args(&["-C", "18446744073709551616", "letters.txt"]) + .succeeds(); assert_eq!(at.read("xaa"), "aaaaaaaaa\nbbbb\ncccc\ndd\nee\n"); } From 4fd598e4d513d3fec00876e3c897c7bd6024508f Mon Sep 17 00:00:00 2001 From: zhitkoff Date: Wed, 6 Sep 2023 13:20:58 -0400 Subject: [PATCH 0058/2851] split: tests --- tests/by-util/test_split.rs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/tests/by-util/test_split.rs b/tests/by-util/test_split.rs index 3ea74606cae..c3e8445a625 100644 --- a/tests/by-util/test_split.rs +++ b/tests/by-util/test_split.rs @@ -377,13 +377,6 @@ fn test_split_obs_lines_standalone() { let glob = Glob::new(&at, ".", r"x[[:alpha:]][[:alpha:]]$"); assert_eq!(glob.count(), 2); assert_eq!(glob.collate(), at.read_bytes(name)); - ucmd.args(&["-99999999999999999991", name]) - .succeeds() - .no_stderr() - .no_stdout(); - let glob = Glob::new(&at, ".", r"x[[:alpha:]][[:alpha:]]$"); - assert_eq!(glob.count(), 2); - assert_eq!(glob.collate(), at.read_bytes(name)); } /// Test for obsolete lines option standalone overflow From 1669a92694985c8e2ad3df661fe457731698d172 Mon Sep 17 00:00:00 2001 From: zhitkoff Date: Wed, 6 Sep 2023 14:02:08 -0400 Subject: [PATCH 0059/2851] split: tests overflow --- tests/by-util/test_split.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/by-util/test_split.rs b/tests/by-util/test_split.rs index c3e8445a625..849fdc7cfec 100644 --- a/tests/by-util/test_split.rs +++ b/tests/by-util/test_split.rs @@ -2,7 +2,7 @@ // // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// spell-checker:ignore xzaaa sixhundredfiftyonebytes ninetyonebytes threebytes asciilowercase fghij klmno pqrst uvwxyz fivelines twohundredfortyonebytes onehundredlines nbbbb dxen +// spell-checker:ignore xzaaa sixhundredfiftyonebytes ninetyonebytes threebytes asciilowercase fghij klmno pqrst uvwxyz fivelines twohundredfortyonebytes onehundredlines nbbbb dxen ncccc use crate::common::util::{AtPath, TestScenario}; use rand::{thread_rng, Rng, SeedableRng}; @@ -796,6 +796,7 @@ fn test_number_kth_of_n() { .args(&["-e", "--number=99/100", "asciilowercase.txt"]) .succeeds() .stdout_only(""); + #[cfg(target_pointer_width = "64")] new_ucmd!() .args(&[ "--number=r/9223372036854775807/18446744073709551615", @@ -811,6 +812,7 @@ fn test_number_kth_of_n() { .args(&["--number=10/5", "asciilowercase.txt"]) .fails() .stderr_contains("split: invalid chunk number: 10"); + #[cfg(target_pointer_width = "64")] new_ucmd!() .args(&[ "--number=9223372036854775807/18446744073709551616", @@ -834,6 +836,7 @@ fn test_number_kth_of_n_round_robin() { .args(&["-e", "--number", "r/7/7", "fivelines.txt"]) .succeeds() .stdout_only(""); + #[cfg(target_pointer_width = "64")] new_ucmd!() .args(&[ "--number", @@ -842,6 +845,7 @@ fn test_number_kth_of_n_round_robin() { ]) .succeeds() .stdout_only(""); + #[cfg(target_pointer_width = "64")] new_ucmd!() .args(&[ "--number", @@ -1018,6 +1022,7 @@ fn test_line_bytes() { #[test] fn test_line_bytes_overflow() { + #[cfg(target_pointer_width = "64")] let (at, mut ucmd) = at_and_ucmd!(); ucmd.args(&["-C", "18446744073709551616", "letters.txt"]) .succeeds(); From fbf5ac4329b83c8c6e6ffb3f87ab7c2fea09e263 Mon Sep 17 00:00:00 2001 From: zhitkoff Date: Wed, 6 Sep 2023 14:16:21 -0400 Subject: [PATCH 0060/2851] split: tests 32bit --- tests/by-util/test_split.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/by-util/test_split.rs b/tests/by-util/test_split.rs index 849fdc7cfec..247b3cc5786 100644 --- a/tests/by-util/test_split.rs +++ b/tests/by-util/test_split.rs @@ -1021,8 +1021,8 @@ fn test_line_bytes() { } #[test] +#[cfg(target_pointer_width = "64")] fn test_line_bytes_overflow() { - #[cfg(target_pointer_width = "64")] let (at, mut ucmd) = at_and_ucmd!(); ucmd.args(&["-C", "18446744073709551616", "letters.txt"]) .succeeds(); From e40e88702270930470d99301118e03eaba37ca6d Mon Sep 17 00:00:00 2001 From: zhitkoff Date: Wed, 6 Sep 2023 18:43:20 -0400 Subject: [PATCH 0061/2851] split: some refactoring for handle_obsolete() --- src/uu/split/src/split.rs | 241 +++++++++++++++++++++++--------------- 1 file changed, 146 insertions(+), 95 deletions(-) diff --git a/src/uu/split/src/split.rs b/src/uu/split/src/split.rs index 6fb05dabb5f..65032dfd9ed 100644 --- a/src/uu/split/src/split.rs +++ b/src/uu/split/src/split.rs @@ -75,104 +75,152 @@ fn handle_obsolete(args: impl uucore::Args) -> (Vec, Option) { let mut obs_lines = None; let mut preceding_long_opt_req_value = false; let mut preceding_short_opt_req_value = false; + let filtered_args = args .filter_map(|os_slice| { - let filter: Option; - if let Some(slice) = os_slice.to_str() { - // check if the slice is a true short option (and not hyphen prefixed value of an option) - // and if so, a short option that can contain obsolete lines value - if slice.starts_with('-') - && !slice.starts_with("--") - && !preceding_long_opt_req_value - && !preceding_short_opt_req_value - && !slice.starts_with("-a") - && !slice.starts_with("-b") - && !slice.starts_with("-C") - && !slice.starts_with("-l") - && !slice.starts_with("-n") - { - // start of the short option string - // that can have obsolete lines option value in it - // extract numeric part and filter it out - let mut obs_lines_extracted: Vec = vec![]; - let mut obs_lines_end_reached = false; - let filtered_slice: Vec = slice - .chars() - .filter(|c| { - // To correctly process scenario like '-x200a4' - // we need to stop extracting digits once alphabetic character is encountered - // after we already have something in obs_lines_extracted - if c.is_ascii_digit() && !obs_lines_end_reached { - obs_lines_extracted.push(*c); - false - } else { - if !obs_lines_extracted.is_empty() { - obs_lines_end_reached = true; - } - true - } - }) - .collect(); - - if obs_lines_extracted.is_empty() { - // no obsolete lines value found/extracted - filter = Some(OsString::from(slice)); - } else { - // obsolete lines value was extracted - obs_lines = Some(obs_lines_extracted.iter().collect()); - if filtered_slice.get(1).is_some() { - // there were some short options in front of or after obsolete lines value - // i.e. '-xd100' or '-100de' or similar, which after extraction of obsolete lines value - // would look like '-xd' or '-de' or similar - let filtered_slice: String = filtered_slice.iter().collect(); - filter = Some(OsString::from(filtered_slice)); - } else { - filter = None; - } - } - } else { - // either not a short option - // or a short option that cannot have obsolete lines value in it - filter = Some(OsString::from(slice)); - } - // capture if current slice is a preceding long option that requires value and does not use '=' to assign that value - // following slice should be treaded as value for this option - // even if it starts with '-' (which would be treated as hyphen prefixed value) - if slice.starts_with("--") { - preceding_long_opt_req_value = &slice[2..] == OPT_BYTES - || &slice[2..] == OPT_LINE_BYTES - || &slice[2..] == OPT_LINES - || &slice[2..] == OPT_ADDITIONAL_SUFFIX - || &slice[2..] == OPT_FILTER - || &slice[2..] == OPT_NUMBER - || &slice[2..] == OPT_SUFFIX_LENGTH; - } - // capture if current slice is a preceding short option that requires value and does not have value in the same slice (value separated by whitespace) - // following slice should be treaded as value for this option - // even if it starts with '-' (which would be treated as hyphen prefixed value) - preceding_short_opt_req_value = slice == "-b" - || slice == "-C" - || slice == "-l" - || slice == "-n" - || slice == "-a"; - // slice is a value - // reset preceding option flags - if !slice.starts_with('-') { - preceding_short_opt_req_value = false; - preceding_long_opt_req_value = false; - } + filter_args( + os_slice, + &mut obs_lines, + &mut preceding_long_opt_req_value, + &mut preceding_short_opt_req_value, + ) + }) + .collect(); + + (filtered_args, obs_lines) +} + +/// Helper function to [`handle_obsolete`] +/// Filters out obsolete lines option from args +fn filter_args( + os_slice: OsString, + obs_lines: &mut Option, + preceding_long_opt_req_value: &mut bool, + preceding_short_opt_req_value: &mut bool, +) -> Option { + let filter: Option; + if let Some(slice) = os_slice.to_str() { + if should_extract_obs_lines( + slice, + preceding_long_opt_req_value, + preceding_short_opt_req_value, + ) { + // start of the short option string + // that can have obsolete lines option value in it + filter = handle_extract_obs_lines(slice, obs_lines); + } else { + // either not a short option + // or a short option that cannot have obsolete lines value in it + filter = Some(OsString::from(slice)); + } + handle_preceding_options( + slice, + preceding_long_opt_req_value, + preceding_short_opt_req_value, + ); + } else { + // Cannot cleanly convert os_slice to UTF-8 + // Do not process and return as-is + // This will cause failure later on, but we should not handle it here + // and let clap panic on invalid UTF-8 argument + filter = Some(os_slice); + } + filter +} + +/// Helper function to [`filter_args`] +/// Checks if the slice is a true short option (and not hyphen prefixed value of an option) +/// and if so, a short option that can contain obsolete lines value +fn should_extract_obs_lines( + slice: &str, + preceding_long_opt_req_value: &bool, + preceding_short_opt_req_value: &bool, +) -> bool { + slice.starts_with('-') + && !slice.starts_with("--") + && !preceding_long_opt_req_value + && !preceding_short_opt_req_value + && !slice.starts_with("-a") + && !slice.starts_with("-b") + && !slice.starts_with("-C") + && !slice.starts_with("-l") + && !slice.starts_with("-n") +} + +/// Helper function to [`filter_args`] +/// Extracts obsolete lines numeric part from argument slice +/// and filters it out +fn handle_extract_obs_lines(slice: &str, obs_lines: &mut Option) -> Option { + let mut obs_lines_extracted: Vec = vec![]; + let mut obs_lines_end_reached = false; + let filtered_slice: Vec = slice + .chars() + .filter(|c| { + // To correctly process scenario like '-x200a4' + // we need to stop extracting digits once alphabetic character is encountered + // after we already have something in obs_lines_extracted + if c.is_ascii_digit() && !obs_lines_end_reached { + obs_lines_extracted.push(*c); + false } else { - // Cannot cleanly convert os_slice to UTF-8 - // Do not process and return as-is - // This will cause failure later on, but we should not handle it here - // and let clap panic on invalid UTF-8 argument - filter = Some(os_slice); + if !obs_lines_extracted.is_empty() { + obs_lines_end_reached = true; + } + true } - // return filter - filter }) .collect(); - (filtered_args, obs_lines) + + if obs_lines_extracted.is_empty() { + // no obsolete lines value found/extracted + Some(OsString::from(slice)) + } else { + // obsolete lines value was extracted + let extracted: String = obs_lines_extracted.iter().collect(); + *obs_lines = Some(extracted); + if filtered_slice.get(1).is_some() { + // there were some short options in front of or after obsolete lines value + // i.e. '-xd100' or '-100de' or similar, which after extraction of obsolete lines value + // would look like '-xd' or '-de' or similar + let filtered_slice: String = filtered_slice.iter().collect(); + Some(OsString::from(filtered_slice)) + } else { + None + } + } +} + +/// Helper function to [`handle_extract_obs_lines`] +/// Captures if current slice is a preceding option +/// that requires value +fn handle_preceding_options( + slice: &str, + preceding_long_opt_req_value: &mut bool, + preceding_short_opt_req_value: &mut bool, +) { + // capture if current slice is a preceding long option that requires value and does not use '=' to assign that value + // following slice should be treaded as value for this option + // even if it starts with '-' (which would be treated as hyphen prefixed value) + if slice.starts_with("--") { + *preceding_long_opt_req_value = &slice[2..] == OPT_BYTES + || &slice[2..] == OPT_LINE_BYTES + || &slice[2..] == OPT_LINES + || &slice[2..] == OPT_ADDITIONAL_SUFFIX + || &slice[2..] == OPT_FILTER + || &slice[2..] == OPT_NUMBER + || &slice[2..] == OPT_SUFFIX_LENGTH; + } + // capture if current slice is a preceding short option that requires value and does not have value in the same slice (value separated by whitespace) + // following slice should be treaded as value for this option + // even if it starts with '-' (which would be treated as hyphen prefixed value) + *preceding_short_opt_req_value = + slice == "-b" || slice == "-C" || slice == "-l" || slice == "-n" || slice == "-a"; + // slice is a value + // reset preceding option flags + if !slice.starts_with('-') { + *preceding_short_opt_req_value = false; + *preceding_long_opt_req_value = false; + } } pub fn uu_app() -> Command { @@ -430,6 +478,9 @@ impl NumberType { /// or if `K` is greater than `N` /// then this function returns [`NumberTypeError`]. fn from(s: &str) -> Result { + fn is_invalid_chunk(chunk_number: u64, num_chunks: u64) -> bool { + chunk_number > num_chunks || chunk_number == 0 + } let parts: Vec<&str> = s.split('/').collect(); match &parts[..] { [n_str] => { @@ -446,7 +497,7 @@ impl NumberType { .map_err(|_| NumberTypeError::NumberOfChunks(n_str.to_string()))?; let chunk_number = parse_size(k_str) .map_err(|_| NumberTypeError::ChunkNumber(k_str.to_string()))?; - if chunk_number > num_chunks || chunk_number == 0 { + if is_invalid_chunk(chunk_number, num_chunks) { return Err(NumberTypeError::ChunkNumber(k_str.to_string())); } Ok(Self::KthBytes(chunk_number, num_chunks)) @@ -461,7 +512,7 @@ impl NumberType { .map_err(|_| NumberTypeError::NumberOfChunks(n_str.to_string()))?; let chunk_number = parse_size(k_str) .map_err(|_| NumberTypeError::ChunkNumber(k_str.to_string()))?; - if chunk_number > num_chunks || chunk_number == 0 { + if is_invalid_chunk(chunk_number, num_chunks) { return Err(NumberTypeError::ChunkNumber(k_str.to_string())); } Ok(Self::KthLines(chunk_number, num_chunks)) @@ -476,7 +527,7 @@ impl NumberType { .map_err(|_| NumberTypeError::NumberOfChunks(n_str.to_string()))?; let chunk_number = parse_size(k_str) .map_err(|_| NumberTypeError::ChunkNumber(k_str.to_string()))?; - if chunk_number > num_chunks || chunk_number == 0 { + if is_invalid_chunk(chunk_number, num_chunks) { return Err(NumberTypeError::ChunkNumber(k_str.to_string())); } Ok(Self::KthRoundRobin(chunk_number, num_chunks)) From 3be284e0d94eb0c8d61e7ab684a9ff1bec5a4196 Mon Sep 17 00:00:00 2001 From: zhitkoff Date: Wed, 6 Sep 2023 19:49:26 -0400 Subject: [PATCH 0062/2851] split: more test coverage --- src/uu/split/src/split.rs | 2 +- tests/by-util/test_split.rs | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/src/uu/split/src/split.rs b/src/uu/split/src/split.rs index 65032dfd9ed..7762789ac6e 100644 --- a/src/uu/split/src/split.rs +++ b/src/uu/split/src/split.rs @@ -1295,7 +1295,7 @@ where // If we would have written zero chunks of output, then terminate // immediately. This happens on `split -e -n 3 /dev/null`, for // example. - if num_chunks == 0 { + if num_chunks == 0 || num_bytes == 0 { return Ok(()); } diff --git a/tests/by-util/test_split.rs b/tests/by-util/test_split.rs index 247b3cc5786..bbac3c73924 100644 --- a/tests/by-util/test_split.rs +++ b/tests/by-util/test_split.rs @@ -340,6 +340,18 @@ fn test_split_lines_number() { .succeeds() .no_stderr() .no_stdout(); + scene + .ucmd() + .args(&["--lines", "0", "file"]) + .fails() + .code_is(1) + .stderr_only("split: invalid number of lines: 0\n"); + scene + .ucmd() + .args(&["-0", "file"]) + .fails() + .code_is(1) + .stderr_only("split: invalid number of lines: 0\n"); scene .ucmd() .args(&["--lines", "2fb", "file"]) @@ -669,6 +681,15 @@ fn test_split_stdin_num_chunks() { .stderr_only("split: -: cannot determine file size\n"); } +#[test] +fn test_split_stdin_num_kth_chunk() { + new_ucmd!() + .args(&["--number=1/2"]) + .fails() + .code_is(1) + .stderr_only("split: -: cannot determine file size\n"); +} + fn file_read(at: &AtPath, filename: &str) -> String { let mut s = String::new(); at.open(filename).read_to_string(&mut s).unwrap(); @@ -784,6 +805,10 @@ fn test_number_n() { assert_eq!(file_read("xac"), "klmno"); assert_eq!(file_read("xad"), "pqrst"); assert_eq!(file_read("xae"), "uvwxyz\n"); + new_ucmd!() + .args(&["--number=100", "/dev/null"]) + .succeeds() + .stdout_only(""); } #[test] @@ -792,10 +817,18 @@ fn test_number_kth_of_n() { .args(&["--number=3/5", "asciilowercase.txt"]) .succeeds() .stdout_only("klmno"); + new_ucmd!() + .args(&["--number=5/5", "asciilowercase.txt"]) + .succeeds() + .stdout_only("uvwxyz\n"); new_ucmd!() .args(&["-e", "--number=99/100", "asciilowercase.txt"]) .succeeds() .stdout_only(""); + new_ucmd!() + .args(&["--number=3/10", "/dev/null"]) + .succeeds() + .stdout_only(""); #[cfg(target_pointer_width = "64")] new_ucmd!() .args(&[ From 8883f016d4ffd9929949130ab724a20b1c3cb132 Mon Sep 17 00:00:00 2001 From: zhitkoff Date: Wed, 6 Sep 2023 20:09:26 -0400 Subject: [PATCH 0063/2851] split: fix windows tests --- tests/by-util/test_split.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/by-util/test_split.rs b/tests/by-util/test_split.rs index bbac3c73924..c204a6238d2 100644 --- a/tests/by-util/test_split.rs +++ b/tests/by-util/test_split.rs @@ -805,6 +805,7 @@ fn test_number_n() { assert_eq!(file_read("xac"), "klmno"); assert_eq!(file_read("xad"), "pqrst"); assert_eq!(file_read("xae"), "uvwxyz\n"); + #[cfg(unix)] new_ucmd!() .args(&["--number=100", "/dev/null"]) .succeeds() @@ -825,6 +826,7 @@ fn test_number_kth_of_n() { .args(&["-e", "--number=99/100", "asciilowercase.txt"]) .succeeds() .stdout_only(""); + #[cfg(unix)] new_ucmd!() .args(&["--number=3/10", "/dev/null"]) .succeeds() From 3f065eed8a265e24ea8790a8680dd62410c5f024 Mon Sep 17 00:00:00 2001 From: zhitkoff Date: Wed, 6 Sep 2023 21:04:01 -0400 Subject: [PATCH 0064/2851] split: fixing test for 32bit --- tests/by-util/test_split.rs | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/tests/by-util/test_split.rs b/tests/by-util/test_split.rs index c204a6238d2..5eba5071f64 100644 --- a/tests/by-util/test_split.rs +++ b/tests/by-util/test_split.rs @@ -657,19 +657,17 @@ fn test_split_overflow_bytes_size() { } #[test] +#[cfg(target_pointer_width = "32")] fn test_split_chunks_num_chunks_oversized_32() { - #[cfg(target_pointer_width = "32")] - { - let scene = TestScenario::new(util_name!()); - let at = &scene.fixtures; - at.touch("file"); - scene - .ucmd() - .args(&["--number", "5000000000", "file"]) - .fails() - .code_is(1) - .stderr_only("split: Number of chunks too big\n"); - } + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + at.touch("file"); + scene + .ucmd() + .args(&["--number", "5000000000", "sixhundredfiftyonebytes.txt"]) + .fails() + .code_is(1) + .stderr_only("split: Number of chunks too big\n"); } #[test] From e3f6fd3c3f6f9d80078789e79e2a80b22708461b Mon Sep 17 00:00:00 2001 From: David CARLIER Date: Thu, 7 Sep 2023 20:48:26 +0100 Subject: [PATCH 0065/2851] truncate clippy fix. --- src/uu/truncate/src/truncate.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/truncate/src/truncate.rs b/src/uu/truncate/src/truncate.rs index 3b1e9bd0910..6e1c19fde9f 100644 --- a/src/uu/truncate/src/truncate.rs +++ b/src/uu/truncate/src/truncate.rs @@ -102,7 +102,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { .unwrap_or_default(); if files.is_empty() { - return Err(UUsageError::new(1, "missing file operand")); + Err(UUsageError::new(1, "missing file operand")) } else { let io_blocks = matches.get_flag(options::IO_BLOCKS); let no_create = matches.get_flag(options::NO_CREATE); From a63603eea81a8999c0e4bd2d5ccc48d2203a3a4b Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Thu, 7 Sep 2023 22:08:11 +0200 Subject: [PATCH 0066/2851] echo's doc doesn't show correctly --- src/uu/echo/echo.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/uu/echo/echo.md b/src/uu/echo/echo.md index 4330ecd8837..f6f6f37ffcd 100644 --- a/src/uu/echo/echo.md +++ b/src/uu/echo/echo.md @@ -12,15 +12,15 @@ Echo the STRING(s) to standard output. If -e is in effect, the following sequences are recognized: -\\ backslash -\a alert (BEL) -\b backspace -\c produce no further output -\e escape -\f form feed -\n new line -\r carriage return -\t horizontal tab -\v vertical tab -\0NNN byte with octal value NNN (1 to 3 digits) -\xHH byte with hexadecimal value HH (1 to 2 digits) \ No newline at end of file +- `\` backslash +- `\a` alert (BEL) +- `\b` backspace +- `\c` produce no further output +- `\e` escape +- `\f` form feed +- `\n` new line +- `\r` carriage return +- `\t` horizontal tab +- `\v` vertical tab +- `\0NNN` byte with octal value NNN (1 to 3 digits) +- `\xHH` byte with hexadecimal value HH (1 to 2 digits) From 6a63acc983d2b4b3e9c6b8f7ec325501ba5523f6 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Fri, 8 Sep 2023 07:27:19 +0200 Subject: [PATCH 0067/2851] deny.toml: add redox_syscall to skip list --- deny.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/deny.toml b/deny.toml index 678fc352dc3..154b2bfe848 100644 --- a/deny.toml +++ b/deny.toml @@ -89,6 +89,8 @@ skip = [ { name = "bitflags", version = "1.3.2" }, # blake2b_simd { name = "constant_time_eq", version = "0.2.6" }, + # various crates + { name = "redox_syscall", version = "0.3.5" }, ] # spell-checker: enable From 7d2996bb1a8511563ee074c94dd0e8c4a84ea60a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 8 Sep 2023 06:51:03 +0000 Subject: [PATCH 0068/2851] chore(deps): update rust crate redox_syscall to 0.4 --- Cargo.lock | 17 +++++++++++++---- Cargo.toml | 2 +- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0f18b8b727f..c6f76c75b75 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -843,7 +843,7 @@ checksum = "d4029edd3e734da6fe05b6cd7bd2960760a616bd2ddd0d59a0124746d6272af0" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.3.5", "windows-sys 0.48.0", ] @@ -1510,7 +1510,7 @@ checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.3.5", "smallvec", "windows-targets 0.48.0", ] @@ -1743,6 +1743,15 @@ dependencies = [ "bitflags 1.3.2", ] +[[package]] +name = "redox_syscall" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded0bce2d41cc3c57aefa284708ced249a64acb01745dbbe72bd78610bfd644c" +dependencies = [ + "bitflags 1.3.2", +] + [[package]] name = "reference-counted-singleton" version = "0.1.2" @@ -2115,7 +2124,7 @@ checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef" dependencies = [ "cfg-if", "fastrand", - "redox_syscall", + "redox_syscall 0.3.5", "rustix 0.38.8", "windows-sys 0.48.0", ] @@ -3085,7 +3094,7 @@ version = "0.0.21" dependencies = [ "clap", "libc", - "redox_syscall", + "redox_syscall 0.4.0", "uucore", ] diff --git a/Cargo.toml b/Cargo.toml index 88047df009e..8ba13859636 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -309,7 +309,7 @@ quick-error = "2.0.1" rand = { version = "0.8", features = ["small_rng"] } rand_core = "0.6" rayon = "1.7" -redox_syscall = "0.3" +redox_syscall = "0.4" regex = "1.9.5" rstest = "0.18.2" rust-ini = "0.19.0" From 2c96a0d741e182f0fc7c28258ee248c5f192ff85 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Mon, 4 Sep 2023 14:46:01 +0200 Subject: [PATCH 0069/2851] Bump nix and ctrlc nix from 0.26.2 -> 0.27.1 ctrlc from 3.4.0 -> 3.4.1 --- Cargo.lock | 17 +++++------------ Cargo.toml | 2 +- 2 files changed, 6 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c6f76c75b75..ff9d65e469c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -684,9 +684,9 @@ dependencies = [ [[package]] name = "ctrlc" -version = "3.4.0" +version = "3.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a011bbe2c35ce9c1f143b7af6f94f29a167beb4cd1d29e6740ce836f723120e" +checksum = "82e95fbd621905b854affdc67943b043a0fbb6ed7385fd5a25650d19a8a6cfdf" dependencies = [ "nix", "windows-sys 0.48.0", @@ -1343,14 +1343,13 @@ dependencies = [ [[package]] name = "nix" -version = "0.26.2" +version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a" +checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.3.3", "cfg-if", "libc", - "static_assertions", ] [[package]] @@ -2076,12 +2075,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - [[package]] name = "strsim" version = "0.10.0" diff --git a/Cargo.toml b/Cargo.toml index 8ba13859636..fa45b2bb179 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -293,7 +293,7 @@ lscolors = { version = "0.15.0", default-features = false, features = [ ] } memchr = "2" memmap2 = "0.7" -nix = { version = "0.26", default-features = false } +nix = { version = "0.27", default-features = false } nom = "7.1.3" notify = { version = "=6.0.1", features = ["macos_kqueue"] } num-bigint = "0.4.4" From 36d5013fac3d4fd4158d721e2fa24b685c775a07 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Thu, 7 Sep 2023 15:07:20 +0200 Subject: [PATCH 0070/2851] stty: adapt to API change in nix 0.27.x tcgetattr(fd: RawFd) changed to tcgetattr(fd: Fd), with RawFd not implementing AsFd. A similar change was applied to tcsetattr. --- src/uu/stty/src/stty.rs | 56 +++++++++++++++++++++++++++++++---------- 1 file changed, 43 insertions(+), 13 deletions(-) diff --git a/src/uu/stty/src/stty.rs b/src/uu/stty/src/stty.rs index d55870730aa..669285750b4 100644 --- a/src/uu/stty/src/stty.rs +++ b/src/uu/stty/src/stty.rs @@ -14,10 +14,12 @@ use nix::sys::termios::{ OutputFlags, SpecialCharacterIndices, Termios, }; use nix::{ioctl_read_bad, ioctl_write_ptr_bad}; -use std::io::{self, stdout}; +use std::fs::File; +use std::io::{self, stdout, Stdout}; use std::ops::ControlFlow; +use std::os::fd::{AsFd, BorrowedFd}; use std::os::unix::fs::OpenOptionsExt; -use std::os::unix::io::{AsRawFd, IntoRawFd, RawFd}; +use std::os::unix::io::{AsRawFd, RawFd}; use uucore::error::{UResult, USimpleError}; use uucore::{format_usage, help_about, help_usage}; @@ -91,10 +93,33 @@ mod options { struct Options<'a> { all: bool, save: bool, - file: RawFd, + file: Device, settings: Option>, } +enum Device { + File(File), + Stdout(Stdout), +} + +impl AsFd for Device { + fn as_fd(&self) -> BorrowedFd<'_> { + match self { + Self::File(f) => f.as_fd(), + Self::Stdout(stdout) => stdout.as_fd(), + } + } +} + +impl AsRawFd for Device { + fn as_raw_fd(&self) -> RawFd { + match self { + Self::File(f) => f.as_raw_fd(), + Self::Stdout(stdout) => stdout.as_raw_fd(), + } + } +} + impl<'a> Options<'a> { fn from(matches: &'a ArgMatches) -> io::Result { Ok(Self { @@ -110,12 +135,13 @@ impl<'a> Options<'a> { // will clean up the FD for us on exit, so it doesn't // matter. The alternative would be to have an enum of // BorrowedFd/OwnedFd to handle both cases. - Some(f) => std::fs::OpenOptions::new() - .read(true) - .custom_flags(O_NONBLOCK) - .open(f)? - .into_raw_fd(), - None => stdout().as_raw_fd(), + Some(f) => Device::File( + std::fs::OpenOptions::new() + .read(true) + .custom_flags(O_NONBLOCK) + .open(f)?, + ), + None => Device::Stdout(stdout()), }, settings: matches .get_many::(options::SETTINGS) @@ -175,7 +201,7 @@ fn stty(opts: &Options) -> UResult<()> { } // TODO: Figure out the right error message for when tcgetattr fails - let mut termios = tcgetattr(opts.file).expect("Could not get terminal attributes"); + let mut termios = tcgetattr(opts.file.as_fd()).expect("Could not get terminal attributes"); if let Some(settings) = &opts.settings { for setting in settings { @@ -187,8 +213,12 @@ fn stty(opts: &Options) -> UResult<()> { } } - tcsetattr(opts.file, nix::sys::termios::SetArg::TCSANOW, &termios) - .expect("Could not write terminal attributes"); + tcsetattr( + opts.file.as_fd(), + nix::sys::termios::SetArg::TCSANOW, + &termios, + ) + .expect("Could not write terminal attributes"); } else { print_settings(&termios, opts).expect("TODO: make proper error here from nix error"); } @@ -228,7 +258,7 @@ fn print_terminal_size(termios: &Termios, opts: &Options) -> nix::Result<()> { if opts.all { let mut size = TermSize::default(); - unsafe { tiocgwinsz(opts.file, &mut size as *mut _)? }; + unsafe { tiocgwinsz(opts.file.as_raw_fd(), &mut size as *mut _)? }; print!("rows {}; columns {}; ", size.rows, size.columns); } From 7679e908184e73035079fdf74382d155ce8c8574 Mon Sep 17 00:00:00 2001 From: David CARLIER Date: Sat, 9 Sep 2023 08:47:08 +0100 Subject: [PATCH 0071/2851] clippy: remove some unnecessary mut removing useless mutability mostly. --- src/uu/cat/src/splice.rs | 2 +- src/uu/cksum/src/cksum.rs | 3 +-- src/uu/env/src/env.rs | 5 ++++- src/uu/hashsum/src/hashsum.rs | 3 +-- src/uu/sort/src/ext_sort.rs | 6 +++--- 5 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/uu/cat/src/splice.rs b/src/uu/cat/src/splice.rs index 5a9e8738ed2..6c2b6d3dac3 100644 --- a/src/uu/cat/src/splice.rs +++ b/src/uu/cat/src/splice.rs @@ -21,7 +21,7 @@ const BUF_SIZE: usize = 1024 * 16; /// copying or not. False means we don't have to. #[inline] pub(super) fn write_fast_using_splice( - handle: &mut InputHandle, + handle: &InputHandle, write_fd: &impl AsRawFd, ) -> CatResult { let (pipe_rd, pipe_wr) = pipe()?; diff --git a/src/uu/cksum/src/cksum.rs b/src/uu/cksum/src/cksum.rs index 6c9c795821b..629bb457fd8 100644 --- a/src/uu/cksum/src/cksum.rs +++ b/src/uu/cksum/src/cksum.rs @@ -207,8 +207,7 @@ fn digest_read( Ok((digest.result_str(), output_size)) } else { // Assume it's SHAKE. result_str() doesn't work with shake (as of 8/30/2016) - let mut bytes = Vec::new(); - bytes.resize((output_bits + 7) / 8, 0); + let mut bytes = vec![0; (output_bits + 7) / 8]; digest.hash_finalize(&mut bytes); Ok((encode(bytes), output_size)) } diff --git a/src/uu/env/src/env.rs b/src/uu/env/src/env.rs index e031e39e31f..d7c9687de60 100644 --- a/src/uu/env/src/env.rs +++ b/src/uu/env/src/env.rs @@ -101,7 +101,7 @@ fn load_config_file(opts: &mut Options) -> UResult<()> { #[cfg(not(windows))] #[allow(clippy::ptr_arg)] -fn build_command<'a, 'b>(args: &'a mut Vec<&'b str>) -> (Cow<'b, str>, &'a [&'b str]) { +fn build_command<'a, 'b>(args: &'a Vec<&'b str>) -> (Cow<'b, str>, &'a [&'b str]) { let progname = Cow::from(args[0]); (progname, &args[1..]) } @@ -303,7 +303,10 @@ fn run_env(args: impl uucore::Args) -> UResult<()> { print_env(opts.line_ending); } else { // we need to execute a command + #[cfg(windows)] let (prog, args) = build_command(&mut opts.program); + #[cfg(not(windows))] + let (prog, args) = build_command(&opts.program); /* * On Unix-like systems Command::status either ends up calling either fork or posix_spawnp diff --git a/src/uu/hashsum/src/hashsum.rs b/src/uu/hashsum/src/hashsum.rs index f8f00f01119..d27b09b98ed 100644 --- a/src/uu/hashsum/src/hashsum.rs +++ b/src/uu/hashsum/src/hashsum.rs @@ -803,8 +803,7 @@ fn digest_reader( Ok(digest.result_str()) } else { // Assume it's SHAKE. result_str() doesn't work with shake (as of 8/30/2016) - let mut bytes = Vec::new(); - bytes.resize((output_bits + 7) / 8, 0); + let mut bytes = vec![0; (output_bits + 7) / 8]; digest.hash_finalize(&mut bytes); Ok(encode(bytes)) } diff --git a/src/uu/sort/src/ext_sort.rs b/src/uu/sort/src/ext_sort.rs index 08de4e33e0e..18309881229 100644 --- a/src/uu/sort/src/ext_sort.rs +++ b/src/uu/sort/src/ext_sort.rs @@ -224,7 +224,7 @@ fn read_write_loop( let mut sender_option = Some(sender); let mut tmp_files = vec![]; loop { - let mut chunk = match receiver.recv() { + let chunk = match receiver.recv() { Ok(it) => it, _ => { return Ok(ReadResult::WroteChunksToFile { tmp_files }); @@ -232,7 +232,7 @@ fn read_write_loop( }; let tmp_file = write::( - &mut chunk, + &chunk, tmp_dir.next_file()?, settings.compress_prog.as_deref(), separator, @@ -262,7 +262,7 @@ fn read_write_loop( /// Write the lines in `chunk` to `file`, separated by `separator`. /// `compress_prog` is used to optionally compress file contents. fn write( - chunk: &mut Chunk, + chunk: &Chunk, file: (File, PathBuf), compress_prog: Option<&str>, separator: u8, From d19b8b78d863de0ccab5b6074c28b46ad0eec90b Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Sun, 10 Sep 2023 15:07:37 +0200 Subject: [PATCH 0072/2851] uucore: turn quoting_style into a feature --- src/uu/dir/Cargo.toml | 2 +- src/uu/ls/Cargo.toml | 7 ++++++- src/uu/vdir/Cargo.toml | 2 +- src/uu/wc/Cargo.toml | 2 +- src/uucore/Cargo.toml | 1 + src/uucore/src/lib/features.rs | 2 ++ src/uucore/src/lib/{mods => features}/quoting_style.rs | 0 src/uucore/src/lib/lib.rs | 3 ++- src/uucore/src/lib/mods.rs | 2 -- 9 files changed, 14 insertions(+), 7 deletions(-) rename src/uucore/src/lib/{mods => features}/quoting_style.rs (100%) diff --git a/src/uu/dir/Cargo.toml b/src/uu/dir/Cargo.toml index 9cc5fbde4ca..2a709a80465 100644 --- a/src/uu/dir/Cargo.toml +++ b/src/uu/dir/Cargo.toml @@ -16,7 +16,7 @@ path = "src/dir.rs" [dependencies] clap = { workspace = true, features = ["env"] } -uucore = { workspace = true, features = ["entries", "fs"] } +uucore = { workspace = true, features = ["entries", "fs", "quoting-style"] } uu_ls = { workspace = true } [[bin]] diff --git a/src/uu/ls/Cargo.toml b/src/uu/ls/Cargo.toml index 24b6947fd53..e4100d07f8b 100644 --- a/src/uu/ls/Cargo.toml +++ b/src/uu/ls/Cargo.toml @@ -23,7 +23,12 @@ term_grid = { workspace = true } terminal_size = { workspace = true } glob = { workspace = true } lscolors = { workspace = true } -uucore = { workspace = true, features = ["entries", "fs", "version-cmp"] } +uucore = { workspace = true, features = [ + "entries", + "fs", + "quoting-style", + "version-cmp", +] } once_cell = { workspace = true } selinux = { workspace = true, optional = true } diff --git a/src/uu/vdir/Cargo.toml b/src/uu/vdir/Cargo.toml index dc57512b1b6..99bcf383b98 100644 --- a/src/uu/vdir/Cargo.toml +++ b/src/uu/vdir/Cargo.toml @@ -16,7 +16,7 @@ path = "src/vdir.rs" [dependencies] clap = { workspace = true, features = ["env"] } -uucore = { workspace = true, features = ["entries", "fs"] } +uucore = { workspace = true, features = ["entries", "fs", "quoting-style"] } uu_ls = { workspace = true } [[bin]] diff --git a/src/uu/wc/Cargo.toml b/src/uu/wc/Cargo.toml index 199cc4e0a29..5712fbd8339 100644 --- a/src/uu/wc/Cargo.toml +++ b/src/uu/wc/Cargo.toml @@ -16,7 +16,7 @@ path = "src/wc.rs" [dependencies] clap = { workspace = true } -uucore = { workspace = true, features = ["pipes"] } +uucore = { workspace = true, features = ["pipes", "quoting-style"] } bytecount = { workspace = true } thiserror = { workspace = true } unicode-width = { workspace = true } diff --git a/src/uucore/Cargo.toml b/src/uucore/Cargo.toml index 15a053ac4cd..90ae74aab44 100644 --- a/src/uucore/Cargo.toml +++ b/src/uucore/Cargo.toml @@ -82,6 +82,7 @@ mode = ["libc"] perms = ["libc", "walkdir"] pipes = [] process = ["libc"] +quoting-style = [] ranges = [] ringbuffer = [] signals = [] diff --git a/src/uucore/src/lib/features.rs b/src/uucore/src/lib/features.rs index 49eee38439b..3a99eb84f43 100644 --- a/src/uucore/src/lib/features.rs +++ b/src/uucore/src/lib/features.rs @@ -16,6 +16,8 @@ pub mod fsext; pub mod lines; #[cfg(feature = "memo")] pub mod memo; +#[cfg(feature = "quoting-style")] +pub mod quoting_style; #[cfg(feature = "ranges")] pub mod ranges; #[cfg(feature = "ringbuffer")] diff --git a/src/uucore/src/lib/mods/quoting_style.rs b/src/uucore/src/lib/features/quoting_style.rs similarity index 100% rename from src/uucore/src/lib/mods/quoting_style.rs rename to src/uucore/src/lib/features/quoting_style.rs diff --git a/src/uucore/src/lib/lib.rs b/src/uucore/src/lib/lib.rs index c0732f069a8..2f28195dae1 100644 --- a/src/uucore/src/lib/lib.rs +++ b/src/uucore/src/lib/lib.rs @@ -25,7 +25,6 @@ pub use crate::mods::error; pub use crate::mods::line_ending; pub use crate::mods::os; pub use crate::mods::panic; -pub use crate::mods::quoting_style; // * string parsing modules pub use crate::parser::parse_glob; @@ -46,6 +45,8 @@ pub use crate::features::fsext; pub use crate::features::lines; #[cfg(feature = "memo")] pub use crate::features::memo; +#[cfg(feature = "quoting-style")] +pub use crate::features::quoting_style; #[cfg(feature = "ranges")] pub use crate::features::ranges; #[cfg(feature = "ringbuffer")] diff --git a/src/uucore/src/lib/mods.rs b/src/uucore/src/lib/mods.rs index d3a2dc19e8b..986536d6dd5 100644 --- a/src/uucore/src/lib/mods.rs +++ b/src/uucore/src/lib/mods.rs @@ -9,5 +9,3 @@ pub mod error; pub mod line_ending; pub mod os; pub mod panic; -// dir and vdir also need access to the quoting_style module -pub mod quoting_style; From 55a62f56b954d9991d41206cfcff47ffcbf98ebe Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 10 Sep 2023 22:42:02 +0000 Subject: [PATCH 0073/2851] chore(deps): update rust crate blake2b_simd to 1.0.2 --- Cargo.lock | 14 ++++---------- Cargo.toml | 2 +- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ff9d65e469c..2601e09959d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -155,13 +155,13 @@ checksum = "630be753d4e58660abd17930c71b647fe46c27ea6b63cc59e1e3851406972e42" [[package]] name = "blake2b_simd" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c2f0dc9a68c6317d884f97cc36cf5a3d20ba14ce404227df55e1af708ab04bc" +checksum = "23285ad32269793932e830392f2fe2f83e26488fd3ec778883a93c8323735780" dependencies = [ "arrayref", "arrayvec", - "constant_time_eq 0.2.6", + "constant_time_eq", ] [[package]] @@ -174,7 +174,7 @@ dependencies = [ "arrayvec", "cc", "cfg-if", - "constant_time_eq 0.3.0", + "constant_time_eq", "digest", ] @@ -354,12 +354,6 @@ dependencies = [ "tiny-keccak", ] -[[package]] -name = "constant_time_eq" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21a53c0a4d288377e7415b53dcfc3c04da5cdc2cc95c8d5ac178b58f0b861ad6" - [[package]] name = "constant_time_eq" version = "0.3.0" diff --git a/Cargo.toml b/Cargo.toml index fa45b2bb179..f457af28e7b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -338,7 +338,7 @@ md-5 = "0.10.5" sha1 = "0.10.5" sha2 = "0.10.7" sha3 = "0.10.8" -blake2b_simd = "1.0.1" +blake2b_simd = "1.0.2" blake3 = "1.4.1" sm3 = "0.4.2" digest = "0.10.7" From 8916dc4087345d18af2e71a26a9e8a098566da4b Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Mon, 11 Sep 2023 07:06:23 +0200 Subject: [PATCH 0074/2851] deny.toml: remove constant_time_eq from skip list --- deny.toml | 2 -- 1 file changed, 2 deletions(-) diff --git a/deny.toml b/deny.toml index 154b2bfe848..c80588f51b1 100644 --- a/deny.toml +++ b/deny.toml @@ -87,8 +87,6 @@ skip = [ { name = "syn", version = "1.0.109" }, # various crates { name = "bitflags", version = "1.3.2" }, - # blake2b_simd - { name = "constant_time_eq", version = "0.2.6" }, # various crates { name = "redox_syscall", version = "0.3.5" }, ] From 6d0bac2842d5f58bc0988fc8b07debfa1dffbe99 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Mon, 11 Sep 2023 16:05:19 +0200 Subject: [PATCH 0075/2851] ls: use OnceCell from std instead of once_cell --- src/uu/ls/src/ls.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 652b978a5b0..18a1a221d96 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -12,8 +12,7 @@ use clap::{ use glob::{MatchOptions, Pattern}; use lscolors::LsColors; use number_prefix::NumberPrefix; -use once_cell::unsync::OnceCell; -use std::num::IntErrorKind; +use std::{cell::OnceCell, num::IntErrorKind}; use std::{collections::HashSet, io::IsTerminal}; #[cfg(windows)] From e131ecdc850c5e9125093316bb3e9a0bfa323c3f Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Fri, 8 Sep 2023 15:07:47 +0200 Subject: [PATCH 0076/2851] Bump chrono from 0.4.28 to 0.4.30 --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2601e09959d..f0863ef8685 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -239,9 +239,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.28" +version = "0.4.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95ed24df0632f708f5f6d8082675bef2596f7084dee3dd55f632290bf35bfe0f" +checksum = "defd4e7873dbddba6c7c91e199c7fcb946abc4a6a4ac3195400bcfb01b5de877" dependencies = [ "android-tzdata", "iana-time-zone", diff --git a/Cargo.toml b/Cargo.toml index f457af28e7b..03f43c26aa4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -263,7 +263,7 @@ binary-heap-plus = "0.5.0" bstr = "1.6" bytecount = "0.6.3" byteorder = "1.4.3" -chrono = { version = "^0.4.28", default-features = false, features = [ +chrono = { version = "^0.4.30", default-features = false, features = [ "std", "alloc", "clock", From 6ce80758d5708d0b04852af2db8b66c7074f9452 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Tue, 12 Sep 2023 15:08:21 +0200 Subject: [PATCH 0077/2851] touch: fix deprecation warnings from chrono datetime_from_str() has been deprecated --- src/uu/touch/src/touch.rs | 27 +++++++++++++++++++-------- tests/by-util/test_touch.rs | 3 +-- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/src/uu/touch/src/touch.rs b/src/uu/touch/src/touch.rs index e9970cb2456..85eb97bc462 100644 --- a/src/uu/touch/src/touch.rs +++ b/src/uu/touch/src/touch.rs @@ -3,10 +3,13 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// spell-checker:ignore (ToDO) filetime datetime lpszfilepath mktime DATETIME subsecond datelike timelike +// spell-checker:ignore (ToDO) filetime datetime lpszfilepath mktime DATETIME datelike timelike // spell-checker:ignore (FORMATS) MMDDhhmm YYYYMMDDHHMM YYMMDDHHMM YYYYMMDDHHMMS -use chrono::{DateTime, Datelike, Duration, Local, NaiveDate, NaiveTime, TimeZone, Timelike, Utc}; +use chrono::{ + DateTime, Datelike, Duration, Local, LocalResult, NaiveDate, NaiveDateTime, NaiveTime, + TimeZone, Timelike, +}; use clap::builder::ValueParser; use clap::{crate_version, Arg, ArgAction, ArgGroup, Command}; use filetime::{set_file_times, set_symlink_file_times, FileTime}; @@ -348,8 +351,8 @@ fn parse_date(s: &str) -> UResult { // Tue Dec 3 ... // ("%c", POSIX_LOCALE_FORMAT), // - if let Ok(parsed) = Local.datetime_from_str(s, format::POSIX_LOCALE) { - return Ok(datetime_to_filetime(&parsed)); + if let Ok(parsed) = NaiveDateTime::parse_from_str(s, format::POSIX_LOCALE) { + return Ok(datetime_to_filetime(&parsed.and_utc())); } // Also support other formats found in the GNU tests like @@ -361,8 +364,8 @@ fn parse_date(s: &str) -> UResult { format::YYYY_MM_DD_HH_MM, format::YYYYMMDDHHMM_OFFSET, ] { - if let Ok(parsed) = Utc.datetime_from_str(s, fmt) { - return Ok(datetime_to_filetime(&parsed)); + if let Ok(parsed) = NaiveDateTime::parse_from_str(s, fmt) { + return Ok(datetime_to_filetime(&parsed.and_utc())); } } @@ -411,9 +414,17 @@ fn parse_timestamp(s: &str) -> UResult { } }; - let mut local = chrono::Local - .datetime_from_str(&ts, format) + let local = NaiveDateTime::parse_from_str(&ts, format) .map_err(|_| USimpleError::new(1, format!("invalid date ts format {}", ts.quote())))?; + let mut local = match chrono::Local.from_local_datetime(&local) { + LocalResult::Single(dt) => dt, + _ => { + return Err(USimpleError::new( + 1, + format!("invalid date ts format {}", ts.quote()), + )) + } + }; // Chrono caps seconds at 59, but 60 is valid. It might be a leap second // or wrap to the next minute. But that doesn't really matter, because we diff --git a/tests/by-util/test_touch.rs b/tests/by-util/test_touch.rs index 942446a3383..c9c0d700e2a 100644 --- a/tests/by-util/test_touch.rs +++ b/tests/by-util/test_touch.rs @@ -5,7 +5,6 @@ // spell-checker:ignore (formats) cymdhm cymdhms mdhm mdhms ymdhm ymdhms datetime mktime use crate::common::util::{AtPath, TestScenario}; -use chrono::TimeZone; use filetime::{self, FileTime}; use std::fs::remove_file; use std::path::PathBuf; @@ -32,7 +31,7 @@ fn set_file_times(at: &AtPath, path: &str, atime: FileTime, mtime: FileTime) { } fn str_to_filetime(format: &str, s: &str) -> FileTime { - let tm = chrono::Utc.datetime_from_str(s, format).unwrap(); + let tm = chrono::NaiveDateTime::parse_from_str(s, format).unwrap(); FileTime::from_unix_time(tm.timestamp(), tm.timestamp_subsec_nanos()) } From 335e8d5464bb18ed2047550aaca570d690501173 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Tue, 12 Sep 2023 16:08:19 +0200 Subject: [PATCH 0078/2851] ci: replace deprecated "command" with "fix" markdownlint-cli2-action deprecated "command" input in favor of "fix" --- .github/workflows/CICD.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index 27378cd9672..01dfdd274c3 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -328,7 +328,7 @@ jobs: RUSTDOCFLAGS="-Dwarnings" cargo doc ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} --no-deps --workspace --document-private-items - uses: DavidAnson/markdownlint-cli2-action@v12 with: - command: fix + fix: "true" globs: | *.md docs/src/*.md From d4217c5a129464cec638c56f06a679830d64ff71 Mon Sep 17 00:00:00 2001 From: Guillaume Ranquet Date: Wed, 6 Sep 2023 16:07:59 +0200 Subject: [PATCH 0079/2851] split: catch broken pipe error for round robin strategy The broken pipe error is not handled in the case of the round robin strategy (typically used with --filter). Align to the other strategies to silence that error in that use case too. fixes #5191 Signed-off-by: Guillaume Ranquet --- src/uu/split/src/split.rs | 14 ++++++++++---- tests/by-util/test_split.rs | 12 ++++++++++++ 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/src/uu/split/src/split.rs b/src/uu/split/src/split.rs index 517031791bd..4a1624d07a5 100644 --- a/src/uu/split/src/split.rs +++ b/src/uu/split/src/split.rs @@ -1282,7 +1282,7 @@ fn split_into_n_chunks_by_line_round_robin( settings: &Settings, reader: &mut R, num_chunks: u64, -) -> UResult<()> +) -> std::io::Result<()> where R: BufRead, { @@ -1293,7 +1293,7 @@ where settings.suffix_length, settings.suffix_type, settings.suffix_start, - )?; + ).map_err(|e| io::Error::new(ErrorKind::Other, format!("{e}")))?; // Create one writer for each chunk. This will create each // of the underlying files (if not in `--filter` mode). @@ -1301,7 +1301,7 @@ where for _ in 0..num_chunks { let filename = filename_iterator .next() - .ok_or_else(|| USimpleError::new(1, "output file suffixes exhausted"))?; + .ok_or_else(|| io::Error::new(ErrorKind::Other, "output file suffixes exhausted"))?; let writer = settings.instantiate_current_writer(filename.as_str())?; writers.push(writer); } @@ -1346,7 +1346,13 @@ fn split(settings: &Settings) -> UResult<()> { kth_chunk_by_line(settings, &mut reader, chunk_number, num_chunks) } Strategy::Number(NumberType::RoundRobin(num_chunks)) => { - split_into_n_chunks_by_line_round_robin(settings, &mut reader, num_chunks) + match split_into_n_chunks_by_line_round_robin(settings, &mut reader, num_chunks) { + Ok(_) => Ok(()), + Err(e) => match e.kind() { + ErrorKind::BrokenPipe => Ok(()), + _ => Err(USimpleError::new(1, format!("{e}"))), + }, + } } Strategy::Number(_) => Err(USimpleError::new(1, "-n mode not yet fully implemented")), Strategy::Lines(chunk_size) => { diff --git a/tests/by-util/test_split.rs b/tests/by-util/test_split.rs index 0b7bbfec6dd..cb777578186 100644 --- a/tests/by-util/test_split.rs +++ b/tests/by-util/test_split.rs @@ -299,6 +299,18 @@ fn test_filter_command_fails() { .fails(); } +#[test] +#[cfg(unix)] +fn test_filter_broken_pipe() { + let (at, mut ucmd) = at_and_ucmd!(); + let name = "filter-big-input"; + + RandomFile::new(&at, name).add_lines(1024 * 10); + ucmd + .args(&["--filter=head -c1 > /dev/null", "-n", "r/1", name]) + .succeeds(); +} + #[test] fn test_split_lines_number() { // Test if stdout/stderr for '--lines' option is correct From aa7b39ae493f6fd888c7a0231eeecc36a64556eb Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Wed, 13 Sep 2023 10:45:38 +0200 Subject: [PATCH 0080/2851] split: fix formatting --- src/uu/split/src/split.rs | 3 ++- tests/by-util/test_split.rs | 3 +-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/uu/split/src/split.rs b/src/uu/split/src/split.rs index 4a1624d07a5..5ba9d9db212 100644 --- a/src/uu/split/src/split.rs +++ b/src/uu/split/src/split.rs @@ -1293,7 +1293,8 @@ where settings.suffix_length, settings.suffix_type, settings.suffix_start, - ).map_err(|e| io::Error::new(ErrorKind::Other, format!("{e}")))?; + ) + .map_err(|e| io::Error::new(ErrorKind::Other, format!("{e}")))?; // Create one writer for each chunk. This will create each // of the underlying files (if not in `--filter` mode). diff --git a/tests/by-util/test_split.rs b/tests/by-util/test_split.rs index cb777578186..aba06162bf5 100644 --- a/tests/by-util/test_split.rs +++ b/tests/by-util/test_split.rs @@ -306,8 +306,7 @@ fn test_filter_broken_pipe() { let name = "filter-big-input"; RandomFile::new(&at, name).add_lines(1024 * 10); - ucmd - .args(&["--filter=head -c1 > /dev/null", "-n", "r/1", name]) + ucmd.args(&["--filter=head -c1 > /dev/null", "-n", "r/1", name]) .succeeds(); } From 2523d3813960666ddf87ea1aa77b78c0b4faa289 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 13 Sep 2023 11:51:31 +0000 Subject: [PATCH 0081/2851] chore(deps): update rust crate libc to 0.2.148 --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f0863ef8685..3362401cc13 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1203,9 +1203,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.147" +version = "0.2.148" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" +checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b" [[package]] name = "libloading" diff --git a/Cargo.toml b/Cargo.toml index 03f43c26aa4..21b0fa30ae2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -287,7 +287,7 @@ glob = "0.3.1" half = "2.3" indicatif = "0.17" itertools = "0.11.0" -libc = "0.2.147" +libc = "0.2.148" lscolors = { version = "0.15.0", default-features = false, features = [ "nu-ansi-term", ] } From 0d07cefee8d287d082610a61ac023d3abb4b8b95 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 14 Sep 2023 17:49:59 +0000 Subject: [PATCH 0082/2851] chore(deps): update codecov/codecov-action action to v4 --- .github/workflows/CICD.yml | 2 +- .github/workflows/GnuTests.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index 01dfdd274c3..36df67de6d9 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -1199,7 +1199,7 @@ jobs: ~/.cargo/bin/grcov . --output-type lcov --output-path "${COVERAGE_REPORT_FILE}" --branch --ignore build.rs --ignore "vendor/*" --ignore "/*" --ignore "[a-zA-Z]:/*" --excl-br-line "^\s*((debug_)?assert(_eq|_ne)?!|#\[derive\()" echo "report=${COVERAGE_REPORT_FILE}" >> $GITHUB_OUTPUT - name: Upload coverage results (to Codecov.io) - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v4 # if: steps.vars.outputs.HAS_CODECOV_TOKEN with: # token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/GnuTests.yml b/.github/workflows/GnuTests.yml index 61f30eba4c1..b524f8fde41 100644 --- a/.github/workflows/GnuTests.yml +++ b/.github/workflows/GnuTests.yml @@ -368,7 +368,7 @@ jobs: grcov . --output-type lcov --output-path "${COVERAGE_REPORT_FILE}" --branch --ignore build.rs --ignore "vendor/*" --ignore "/*" --ignore "[a-zA-Z]:/*" --excl-br-line "^\s*((debug_)?assert(_eq|_ne)?!|#\[derive\()" echo "report=${COVERAGE_REPORT_FILE}" >> $GITHUB_OUTPUT - name: Upload coverage results (to Codecov.io) - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v4 with: file: ${{ steps.coverage.outputs.report }} flags: gnutests From dec788dec30339a6e0337664949b8c7c4dfc5109 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Fri, 15 Sep 2023 09:25:04 +0200 Subject: [PATCH 0083/2851] Revert "chore(deps): update codecov/codecov-action action to v4" --- .github/workflows/CICD.yml | 2 +- .github/workflows/GnuTests.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index 36df67de6d9..01dfdd274c3 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -1199,7 +1199,7 @@ jobs: ~/.cargo/bin/grcov . --output-type lcov --output-path "${COVERAGE_REPORT_FILE}" --branch --ignore build.rs --ignore "vendor/*" --ignore "/*" --ignore "[a-zA-Z]:/*" --excl-br-line "^\s*((debug_)?assert(_eq|_ne)?!|#\[derive\()" echo "report=${COVERAGE_REPORT_FILE}" >> $GITHUB_OUTPUT - name: Upload coverage results (to Codecov.io) - uses: codecov/codecov-action@v4 + uses: codecov/codecov-action@v3 # if: steps.vars.outputs.HAS_CODECOV_TOKEN with: # token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/GnuTests.yml b/.github/workflows/GnuTests.yml index b524f8fde41..61f30eba4c1 100644 --- a/.github/workflows/GnuTests.yml +++ b/.github/workflows/GnuTests.yml @@ -368,7 +368,7 @@ jobs: grcov . --output-type lcov --output-path "${COVERAGE_REPORT_FILE}" --branch --ignore build.rs --ignore "vendor/*" --ignore "/*" --ignore "[a-zA-Z]:/*" --excl-br-line "^\s*((debug_)?assert(_eq|_ne)?!|#\[derive\()" echo "report=${COVERAGE_REPORT_FILE}" >> $GITHUB_OUTPUT - name: Upload coverage results (to Codecov.io) - uses: codecov/codecov-action@v4 + uses: codecov/codecov-action@v3 with: file: ${{ steps.coverage.outputs.report }} flags: gnutests From e59285e2766abfa3a04c71945cbc1eb9f5d0b327 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Fri, 15 Sep 2023 08:11:28 +0200 Subject: [PATCH 0084/2851] deny.toml: add terminal_size to skip list --- deny.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/deny.toml b/deny.toml index c80588f51b1..b1707d035cb 100644 --- a/deny.toml +++ b/deny.toml @@ -89,6 +89,8 @@ skip = [ { name = "bitflags", version = "1.3.2" }, # various crates { name = "redox_syscall", version = "0.3.5" }, + # clap_builder, textwrap + { name = "terminal_size", version = "0.2.6" }, ] # spell-checker: enable From b8ff52f28b729efa44127adab2070792f37ae746 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 15 Sep 2023 15:59:41 +0000 Subject: [PATCH 0085/2851] chore(deps): update rust crate chrono to ^0.4.31 --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3362401cc13..3a0e176426d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -239,9 +239,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.30" +version = "0.4.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "defd4e7873dbddba6c7c91e199c7fcb946abc4a6a4ac3195400bcfb01b5de877" +checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" dependencies = [ "android-tzdata", "iana-time-zone", diff --git a/Cargo.toml b/Cargo.toml index 21b0fa30ae2..b15bf4e088e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -263,7 +263,7 @@ binary-heap-plus = "0.5.0" bstr = "1.6" bytecount = "0.6.3" byteorder = "1.4.3" -chrono = { version = "^0.4.30", default-features = false, features = [ +chrono = { version = "^0.4.31", default-features = false, features = [ "std", "alloc", "clock", From 37ee889003509f701e83d33fcb07b9dc30b88aff Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Fri, 15 Sep 2023 22:34:17 +0200 Subject: [PATCH 0086/2851] ls -l: show an error when symlink not readable switching to match and handle the error Will help with tests/ls/stat-failed.sh --- src/uu/ls/src/ls.rs | 97 ++++++++++++++++++++++------------------ tests/by-util/test_ls.rs | 19 ++++++++ 2 files changed, 72 insertions(+), 44 deletions(-) diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 18a1a221d96..8d559ed15f1 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -2908,55 +2908,64 @@ fn display_file_name( && path.file_type(out).unwrap().is_symlink() && !path.must_dereference { - if let Ok(target) = path.p_buf.read_link() { - name.push_str(" -> "); - - // We might as well color the symlink output after the arrow. - // This makes extra system calls, but provides important information that - // people run `ls -l --color` are very interested in. - if let Some(ls_colors) = &config.color { - // We get the absolute path to be able to construct PathData with valid Metadata. - // This is because relative symlinks will fail to get_metadata. - let mut absolute_target = target.clone(); - if target.is_relative() { - if let Some(parent) = path.p_buf.parent() { - absolute_target = parent.join(absolute_target); + match path.p_buf.read_link() { + Ok(target) => { + name.push_str(" -> "); + + // We might as well color the symlink output after the arrow. + // This makes extra system calls, but provides important information that + // people run `ls -l --color` are very interested in. + if let Some(ls_colors) = &config.color { + // We get the absolute path to be able to construct PathData with valid Metadata. + // This is because relative symlinks will fail to get_metadata. + let mut absolute_target = target.clone(); + if target.is_relative() { + if let Some(parent) = path.p_buf.parent() { + absolute_target = parent.join(absolute_target); + } } - } - let target_data = PathData::new(absolute_target, None, None, config, false); + let target_data = PathData::new(absolute_target, None, None, config, false); - // If we have a symlink to a valid file, we use the metadata of said file. - // Because we use an absolute path, we can assume this is guaranteed to exist. - // Otherwise, we use path.md(), which will guarantee we color to the same - // color of non-existent symlinks according to style_for_path_with_metadata. - if path.md(out).is_none() - && get_metadata(target_data.p_buf.as_path(), target_data.must_dereference) - .is_err() - { - name.push_str(&path.p_buf.read_link().unwrap().to_string_lossy()); + // If we have a symlink to a valid file, we use the metadata of said file. + // Because we use an absolute path, we can assume this is guaranteed to exist. + // Otherwise, we use path.md(), which will guarantee we color to the same + // color of non-existent symlinks according to style_for_path_with_metadata. + if path.md(out).is_none() + && get_metadata(target_data.p_buf.as_path(), target_data.must_dereference) + .is_err() + { + name.push_str(&path.p_buf.read_link().unwrap().to_string_lossy()); + } else { + // Use fn get_metadata instead of md() here and above because ls + // should not exit with an err, if we are unable to obtain the target_metadata + let target_metadata = match get_metadata( + target_data.p_buf.as_path(), + target_data.must_dereference, + ) { + Ok(md) => md, + Err(_) => path.md(out).unwrap().to_owned(), + }; + + name.push_str(&color_name( + escape_name(target.as_os_str(), &config.quoting_style), + &target_data.p_buf, + Some(&target_metadata), + ls_colors, + )); + } } else { - // Use fn get_metadata instead of md() here and above because ls - // should not exit with an err, if we are unable to obtain the target_metadata - let target_metadata = match get_metadata( - target_data.p_buf.as_path(), - target_data.must_dereference, - ) { - Ok(md) => md, - Err(_) => path.md(out).unwrap().to_owned(), - }; - - name.push_str(&color_name( - escape_name(target.as_os_str(), &config.quoting_style), - &target_data.p_buf, - Some(&target_metadata), - ls_colors, - )); + // If no coloring is required, we just use target as is. + // Apply the right quoting + name.push_str(&escape_name(target.as_os_str(), &config.quoting_style)); } - } else { - // If no coloring is required, we just use target as is. - // Apply the right quoting - name.push_str(&escape_name(target.as_os_str(), &config.quoting_style)); + } + Err(err) => { + show!(LsError::IOErrorContext( + err, + path.p_buf.to_path_buf(), + false + )); } } } diff --git a/tests/by-util/test_ls.rs b/tests/by-util/test_ls.rs index f1815035834..5f2b7e44350 100644 --- a/tests/by-util/test_ls.rs +++ b/tests/by-util/test_ls.rs @@ -3503,3 +3503,22 @@ fn test_invalid_utf8() { at.touch(filename); ucmd.succeeds(); } + +#[cfg(all(unix, feature = "chmod"))] +#[test] +fn test_ls_perm_io_errors() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + at.mkdir("d"); + at.symlink_file("/", "d/s"); + + scene.ccmd("chmod").arg("600").arg("d").succeeds(); + + scene + .ucmd() + .arg("-l") + .arg("d") + .fails() + .code_is(1) + .stderr_contains("Permission denied"); +} From f9c91fda2f6bc8391f0bc33e102378d3740ec16b Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sat, 16 Sep 2023 15:37:23 +0200 Subject: [PATCH 0087/2851] Document how to run the pure Rust tests --- CONTRIBUTING.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index bd87e2d05ba..6f67eb828db 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -137,6 +137,12 @@ If you also want to test the core utilities: cargo test -p uucore -p coreutils ``` +Or to test the pure Rust tests in the utility itself: + +```shell +cargo test -p uu_ls --lib +``` + Running the complete test suite might take a while. We use [nextest](https://nexte.st/index.html) in the CI and you might want to try it out locally. It can speed up the execution time of the whole test run significantly if the cpu has multiple cores. From 975b3286308adbaea9c1ea1e1df16f33498f14fc Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 17 Sep 2023 08:36:43 +0000 Subject: [PATCH 0088/2851] chore(deps): update rust crate terminal_size to 0.3.0 --- Cargo.lock | 16 +++++++++++++--- Cargo.toml | 2 +- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3a0e176426d..9b079d0907e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -279,7 +279,7 @@ dependencies = [ "anstyle", "clap_lex", "strsim", - "terminal_size", + "terminal_size 0.2.6", ] [[package]] @@ -2135,6 +2135,16 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "terminal_size" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7" +dependencies = [ + "rustix 0.38.8", + "windows-sys 0.48.0", +] + [[package]] name = "textwrap" version = "0.16.0" @@ -2142,7 +2152,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" dependencies = [ "smawk", - "terminal_size", + "terminal_size 0.2.6", "unicode-linebreak", "unicode-width", ] @@ -2671,7 +2681,7 @@ dependencies = [ "once_cell", "selinux", "term_grid", - "terminal_size", + "terminal_size 0.3.0", "unicode-width", "uucore", ] diff --git a/Cargo.toml b/Cargo.toml index b15bf4e088e..f7a3a372541 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -320,7 +320,7 @@ signal-hook = "0.3.17" smallvec = { version = "1.11", features = ["union"] } tempfile = "3.8.0" term_grid = "0.1.5" -terminal_size = "0.2.6" +terminal_size = "0.3.0" textwrap = { version = "0.16.0", features = ["terminal_size"] } thiserror = "1.0" time = { version = "0.3" } From 416a4832b80596eb017869e5cfb8ca4851ae6c0d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 17 Sep 2023 17:46:16 +0000 Subject: [PATCH 0089/2851] fix(deps): update rust crate dns-lookup to 2.0.3 --- Cargo.lock | 4 ++-- src/uucore/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9b079d0907e..4efd163a3f1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -746,9 +746,9 @@ dependencies = [ [[package]] name = "dns-lookup" -version = "2.0.2" +version = "2.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f332aa79f9e9de741ac013237294ef42ce2e9c6394dc7d766725812f1238812" +checksum = "8d0fa3cd8dc96ada974e126a940d37d4079bbbe6a24aca15b1113c2f362441c5" dependencies = [ "cfg-if", "libc", diff --git a/src/uucore/Cargo.toml b/src/uucore/Cargo.toml index 90ae74aab44..93bc618cee2 100644 --- a/src/uucore/Cargo.toml +++ b/src/uucore/Cargo.toml @@ -20,7 +20,7 @@ path = "src/lib/lib.rs" [dependencies] clap = { workspace = true } uucore_procs = { workspace = true } -dns-lookup = { version = "2.0.2", optional = true } +dns-lookup = { version = "2.0.3", optional = true } dunce = { version = "1.0.4", optional = true } wild = "2.1" glob = { workspace = true } From d01fe6a10f4e325de8b3c5bf4fdd5c221b47ca03 Mon Sep 17 00:00:00 2001 From: Rojin Raju <46747837+rojin254@users.noreply.github.com> Date: Tue, 19 Sep 2023 01:17:02 +0530 Subject: [PATCH 0090/2851] ls.rs: rename variable name dfn to displayed_file Closes: #5282 --- src/uu/ls/src/ls.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 8d559ed15f1..cc741fcf229 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -2482,13 +2482,13 @@ fn display_item_long( } }; - let dfn = display_file_name(item, config, None, String::new(), out).contents; + let displayed_file = display_file_name(item, config, None, String::new(), out).contents; write!( out, " {} {}{}", display_date(md, config), - dfn, + displayed_file, config.line_ending )?; } else { @@ -2561,7 +2561,7 @@ fn display_item_long( write!(out, " {}", pad_right("?", padding.uname))?; } - let dfn = display_file_name(item, config, None, String::new(), out).contents; + let displayed_file = display_file_name(item, config, None, String::new(), out).contents; let date_len = 12; writeln!( @@ -2569,7 +2569,7 @@ fn display_item_long( " {} {} {}", pad_left("?", padding.size), pad_left("?", date_len), - dfn, + displayed_file, )?; } From 514315462c7e429599b89a7a6da38e5efadacda4 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 18 Sep 2023 19:47:21 +0000 Subject: [PATCH 0091/2851] chore(deps): update mfinelli/setup-shfmt action to v3 --- .github/workflows/CheckScripts.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/CheckScripts.yml b/.github/workflows/CheckScripts.yml index 98ae6cb75ab..995b5456f18 100644 --- a/.github/workflows/CheckScripts.yml +++ b/.github/workflows/CheckScripts.yml @@ -52,7 +52,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Setup shfmt - uses: mfinelli/setup-shfmt@v2 + uses: mfinelli/setup-shfmt@v3 - name: Run shfmt shell: bash run: | From 1107fadca9e263b8aa976d82ac61be7952d068ae Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Tue, 19 Sep 2023 10:47:00 +0200 Subject: [PATCH 0092/2851] nl: increase line number over multiple files --- src/uu/nl/src/nl.rs | 33 ++++++++++++++++++++++++--------- tests/by-util/test_nl.rs | 13 +++++++++++++ 2 files changed, 37 insertions(+), 9 deletions(-) diff --git a/src/uu/nl/src/nl.rs b/src/uu/nl/src/nl.rs index 61a0a9f35f4..3568822377f 100644 --- a/src/uu/nl/src/nl.rs +++ b/src/uu/nl/src/nl.rs @@ -55,6 +55,20 @@ impl Default for Settings { } } +struct Stats { + line_number: i64, + consecutive_empty_lines: u64, +} + +impl Stats { + fn new(starting_line_number: i64) -> Self { + Self { + line_number: starting_line_number, + consecutive_empty_lines: 0, + } + } +} + // NumberingStyle stores which lines are to be numbered. // The possible options are: // 1. Number all lines @@ -160,6 +174,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { None => vec!["-".to_owned()], }; + let mut stats = Stats::new(settings.starting_line_number); + for file in &files { if file == "-" { // If both file names and '-' are specified, we choose to treat first all @@ -170,12 +186,12 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let path = Path::new(file); let reader = File::open(path).map_err_context(|| file.to_string())?; let mut buffer = BufReader::new(reader); - nl(&mut buffer, &settings)?; + nl(&mut buffer, &mut stats, &settings)?; } if read_stdin { let mut buffer = BufReader::new(stdin()); - nl(&mut buffer, &settings)?; + nl(&mut buffer, &mut stats, &settings)?; } Ok(()) } @@ -285,10 +301,9 @@ pub fn uu_app() -> Command { } // nl implements the main functionality for an individual buffer. -fn nl(reader: &mut BufReader, settings: &Settings) -> UResult<()> { +fn nl(reader: &mut BufReader, stats: &mut Stats, settings: &Settings) -> UResult<()> { let mut current_numbering_style = &settings.body_numbering; - let mut line_no = settings.starting_line_number; - let mut consecutive_empty_lines = 0; + let mut consecutive_empty_lines = stats.consecutive_empty_lines; for line in reader.lines() { let line = line.map_err_context(|| "could not read line".to_string())?; @@ -312,7 +327,7 @@ fn nl(reader: &mut BufReader, settings: &Settings) -> UResult<()> { if let Some(new_style) = new_numbering_style { current_numbering_style = new_style; if settings.renumber { - line_no = settings.starting_line_number; + stats.line_number = settings.starting_line_number; } println!(); } else { @@ -336,13 +351,13 @@ fn nl(reader: &mut BufReader, settings: &Settings) -> UResult<()> { "{}{}{}", settings .number_format - .format(line_no, settings.number_width), + .format(stats.line_number, settings.number_width), settings.number_separator, line ); // update line number for the potential next line - match line_no.checked_add(settings.line_increment) { - Some(new_line_no) => line_no = new_line_no, + match stats.line_number.checked_add(settings.line_increment) { + Some(new_line_number) => stats.line_number = new_line_number, None => return Err(USimpleError::new(1, "line number overflow")), } } else { diff --git a/tests/by-util/test_nl.rs b/tests/by-util/test_nl.rs index b58c0c206fb..336ab4c29af 100644 --- a/tests/by-util/test_nl.rs +++ b/tests/by-util/test_nl.rs @@ -311,6 +311,19 @@ fn test_default_body_numbering() { .stdout_is(" 1\ta\n \n 2\tb\n"); } +#[test] +fn test_default_body_numbering_multiple_files() { + let (at, mut ucmd) = at_and_ucmd!(); + + at.write("a.txt", "a"); + at.write("b.txt", "b"); + at.write("c.txt", "c"); + + ucmd.args(&["a.txt", "b.txt", "c.txt"]) + .succeeds() + .stdout_is(" 1\ta\n 2\tb\n 3\tc\n"); +} + #[test] fn test_body_numbering_all_lines_without_delimiter() { for arg in ["-ba", "--body-numbering=a"] { From 1a30a1b8b656afe0deb674a4b848f7b311a18c81 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Tue, 19 Sep 2023 10:47:20 +0200 Subject: [PATCH 0093/2851] nl: support --join-blank-lines over multiple files --- src/uu/nl/src/nl.rs | 7 +++---- tests/by-util/test_nl.rs | 25 +++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/src/uu/nl/src/nl.rs b/src/uu/nl/src/nl.rs index 3568822377f..fef9c030a2e 100644 --- a/src/uu/nl/src/nl.rs +++ b/src/uu/nl/src/nl.rs @@ -303,15 +303,14 @@ pub fn uu_app() -> Command { // nl implements the main functionality for an individual buffer. fn nl(reader: &mut BufReader, stats: &mut Stats, settings: &Settings) -> UResult<()> { let mut current_numbering_style = &settings.body_numbering; - let mut consecutive_empty_lines = stats.consecutive_empty_lines; for line in reader.lines() { let line = line.map_err_context(|| "could not read line".to_string())?; if line.is_empty() { - consecutive_empty_lines += 1; + stats.consecutive_empty_lines += 1; } else { - consecutive_empty_lines = 0; + stats.consecutive_empty_lines = 0; }; // FIXME section delimiters are hardcoded and settings.section_delimiter is ignored @@ -336,7 +335,7 @@ fn nl(reader: &mut BufReader, stats: &mut Stats, settings: &Settings // for numbering, and only number the last one NumberingStyle::All if line.is_empty() - && consecutive_empty_lines % settings.join_blank_lines != 0 => + && stats.consecutive_empty_lines % settings.join_blank_lines != 0 => { false } diff --git a/tests/by-util/test_nl.rs b/tests/by-util/test_nl.rs index 336ab4c29af..b008c61de77 100644 --- a/tests/by-util/test_nl.rs +++ b/tests/by-util/test_nl.rs @@ -284,6 +284,31 @@ fn test_join_blank_lines() { } } +#[test] +fn test_join_blank_lines_multiple_files() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + at.write("a.txt", "\n\n"); + at.write("b.txt", "\n\n"); + at.write("c.txt", "\n\n"); + + for arg in ["-l3", "--join-blank-lines=3"] { + scene + .ucmd() + .args(&[arg, "--body-numbering=a", "a.txt", "b.txt", "c.txt"]) + .succeeds() + .stdout_is(concat!( + " \n", + " \n", + " 1\t\n", + " \n", + " \n", + " 2\t\n", + )); + } +} + #[test] fn test_join_blank_lines_zero() { for arg in ["-l0", "--join-blank-lines=0"] { From 9443985b49cbf630165d003ec6b1984db94d2b8c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 19 Sep 2023 18:13:47 +0000 Subject: [PATCH 0094/2851] chore(deps): update rust crate unicode-width to 0.1.11 --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4efd163a3f1..ece92cdfe46 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2241,9 +2241,9 @@ checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" [[package]] name = "unicode-width" -version = "0.1.10" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" +checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" [[package]] name = "unicode-xid" diff --git a/Cargo.toml b/Cargo.toml index f7a3a372541..866b1f58549 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -325,7 +325,7 @@ textwrap = { version = "0.16.0", features = ["terminal_size"] } thiserror = "1.0" time = { version = "0.3" } unicode-segmentation = "1.10.1" -unicode-width = "0.1.10" +unicode-width = "0.1.11" utf-8 = "0.7.6" walkdir = "2.4" winapi-util = "0.1.5" From 5586e7bf2bae7d165861e884004aa3c751171d82 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 20 Sep 2023 04:47:16 +0000 Subject: [PATCH 0095/2851] chore(deps): update davidanson/markdownlint-cli2-action action to v13 --- .github/workflows/CICD.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index 01dfdd274c3..577e235f9cf 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -326,7 +326,7 @@ jobs: shell: bash run: | RUSTDOCFLAGS="-Dwarnings" cargo doc ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} --no-deps --workspace --document-private-items - - uses: DavidAnson/markdownlint-cli2-action@v12 + - uses: DavidAnson/markdownlint-cli2-action@v13 with: fix: "true" globs: | From 9b4d2c6bc46cbedfa377e592d6188a643a88e3ed Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Wed, 20 Sep 2023 08:17:46 +0200 Subject: [PATCH 0096/2851] ls: implement --dired * Support ls --dired * stat-failed.sh: update of the test - we have a small difference * ls --dired: address some of the comments * fix warnings * use unwrap() * Improve test Co-authored-by: Daniel Hofstetter * Simplify test Co-authored-by: Daniel Hofstetter * Remove a word from the spell ignore Co-authored-by: Daniel Hofstetter * remove duplication of the spell ignore Co-authored-by: Daniel Hofstetter * rustfmt --------- Co-authored-by: Daniel Hofstetter --- src/uu/ls/src/dired.rs | 178 +++++++++++++++++++ src/uu/ls/src/ls.rs | 172 +++++++++++++----- src/uucore/src/lib/features/quoting_style.rs | 81 ++++++++- tests/by-util/test_ls.rs | 125 ++++++++++++- util/build-gnu.sh | 6 +- 5 files changed, 510 insertions(+), 52 deletions(-) create mode 100644 src/uu/ls/src/dired.rs diff --git a/src/uu/ls/src/dired.rs b/src/uu/ls/src/dired.rs new file mode 100644 index 00000000000..f66d22b38dd --- /dev/null +++ b/src/uu/ls/src/dired.rs @@ -0,0 +1,178 @@ +// This file is part of the uutils coreutils package. +// +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. +// spell-checker:ignore dired subdired + +use crate::Config; +use std::fmt; +use std::io::{BufWriter, Stdout, Write}; +use uucore::error::UResult; + +#[derive(Debug, Clone)] +pub struct BytePosition { + pub start: usize, + pub end: usize, +} + +/// Represents the output structure for DIRED, containing positions for both DIRED and SUBDIRED. +#[derive(Debug, Clone, Default)] +pub struct DiredOutput { + pub dired_positions: Vec, + pub subdired_positions: Vec, + pub just_printed_total: bool, +} + +impl fmt::Display for BytePosition { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{} {}", self.start, self.end) + } +} + +// When --dired is used, all lines starts with 2 spaces +static DIRED_TRAILING_OFFSET: usize = 2; + +/// Calculates the byte positions for DIRED +pub fn calculate_dired_byte_positions( + output_display_len: usize, + dfn_len: usize, + dired_positions: &[BytePosition], +) -> (usize, usize) { + let offset_from_previous_line = if let Some(last_position) = dired_positions.last() { + last_position.end + 1 + } else { + 0 + }; + + let start = output_display_len + offset_from_previous_line; + let end = start + dfn_len; + (start, end) +} + +pub fn indent(out: &mut BufWriter) -> UResult<()> { + write!(out, " ")?; + Ok(()) +} + +pub fn calculate_offset_and_push(dired: &mut DiredOutput, path_len: usize) { + let offset = if dired.subdired_positions.is_empty() { + DIRED_TRAILING_OFFSET + } else { + dired.subdired_positions[dired.subdired_positions.len() - 1].start + DIRED_TRAILING_OFFSET + }; + dired.subdired_positions.push(BytePosition { + start: offset, + end: path_len + offset, + }); +} + +/// Prints the dired output based on the given configuration and dired structure. +pub fn print_dired_output( + config: &Config, + dired: &DiredOutput, + out: &mut BufWriter, +) -> UResult<()> { + out.flush()?; + if config.recursive { + print_positions("//SUBDIRED//", &dired.subdired_positions); + } else if !dired.just_printed_total { + print_positions("//DIRED//", &dired.dired_positions); + } + println!("//DIRED-OPTIONS// --quoting-style={}", config.quoting_style); + Ok(()) +} + +/// Helper function to print positions with a given prefix. +fn print_positions(prefix: &str, positions: &Vec) { + print!("{}", prefix); + for c in positions { + print!(" {}", c); + } + println!(); +} + +pub fn add_total(total_len: usize, dired: &mut DiredOutput) { + dired.just_printed_total = true; + dired.dired_positions.push(BytePosition { + start: 0, + // the 2 is from the trailing spaces + // the 1 is from the line ending (\n) + end: total_len + DIRED_TRAILING_OFFSET - 1, + }); +} + +/// Calculates byte positions and updates the dired structure. +pub fn calculate_and_update_positions( + output_display_len: usize, + dfn_len: usize, + dired: &mut DiredOutput, +) { + let offset = dired + .dired_positions + .last() + .map_or(DIRED_TRAILING_OFFSET, |last_position| { + last_position.start + DIRED_TRAILING_OFFSET + }); + let start = output_display_len + offset + DIRED_TRAILING_OFFSET; + let end = start + dfn_len; + update_positions(start, end, dired, true); +} + +/// Updates the dired positions based on the given start and end positions. +/// update when it is the first element in the list (to manage "total X" +/// insert when it isn't the about total +pub fn update_positions(start: usize, end: usize, dired: &mut DiredOutput, adjust: bool) { + if dired.just_printed_total { + if let Some(last_position) = dired.dired_positions.last_mut() { + *last_position = BytePosition { + start: if adjust { + start + last_position.end + } else { + start + }, + end: if adjust { end + last_position.end } else { end }, + }; + dired.just_printed_total = false; + } + } else { + dired.dired_positions.push(BytePosition { start, end }); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_calculate_dired_byte_positions() { + let output_display = "sample_output".to_string(); + let dfn = "sample_file".to_string(); + let dired_positions = vec![BytePosition { start: 5, end: 10 }]; + let (start, end) = + calculate_dired_byte_positions(output_display.len(), dfn.len(), &dired_positions); + + assert_eq!(start, 24); + assert_eq!(end, 35); + } + + #[test] + fn test_dired_update_positions() { + let mut dired = DiredOutput { + dired_positions: vec![BytePosition { start: 5, end: 10 }], + subdired_positions: vec![], + just_printed_total: true, + }; + + // Test with adjust = true + update_positions(15, 20, &mut dired, true); + let last_position = dired.dired_positions.last().unwrap(); + assert_eq!(last_position.start, 25); // 15 + 10 (end of the previous position) + assert_eq!(last_position.end, 30); // 20 + 10 (end of the previous position) + + // Test with adjust = false + update_positions(30, 35, &mut dired, false); + let last_position = dired.dired_positions.last().unwrap(); + assert_eq!(last_position.start, 30); + assert_eq!(last_position.end, 35); + } +} diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index cc741fcf229..3d67cccaad1 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -3,7 +3,7 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// spell-checker:ignore (ToDO) cpio svgz webm somegroup nlink rmvb xspf tabsize dired +// spell-checker:ignore (ToDO) cpio svgz webm somegroup nlink rmvb xspf tabsize dired subdired use clap::{ builder::{NonEmptyStringValueParser, ValueParser}, @@ -61,7 +61,8 @@ use uucore::{ version_cmp::version_cmp, }; use uucore::{help_about, help_section, help_usage, parse_glob, show, show_error, show_warning}; - +mod dired; +use dired::DiredOutput; #[cfg(not(feature = "selinux"))] static CONTEXT_HELP_TEXT: &str = "print any security context of each file (not enabled)"; #[cfg(feature = "selinux")] @@ -167,6 +168,7 @@ enum LsError { IOError(std::io::Error), IOErrorContext(std::io::Error, PathBuf, bool), BlockSizeParseError(String), + ConflictingArgumentDired(), AlreadyListedError(PathBuf), TimeStyleParseError(String, Vec), } @@ -179,6 +181,7 @@ impl UError for LsError { Self::IOErrorContext(_, _, false) => 1, Self::IOErrorContext(_, _, true) => 2, Self::BlockSizeParseError(_) => 1, + Self::ConflictingArgumentDired() => 1, Self::AlreadyListedError(_) => 2, Self::TimeStyleParseError(_, _) => 1, } @@ -193,6 +196,10 @@ impl Display for LsError { Self::BlockSizeParseError(s) => { write!(f, "invalid --block-size argument {}", s.quote()) } + Self::ConflictingArgumentDired() => { + write!(f, "--dired requires --format=long") + } + Self::TimeStyleParseError(s, possible_time_styles) => { write!( f, @@ -406,6 +413,7 @@ pub struct Config { selinux_supported: bool, group_directories_first: bool, line_ending: LineEnding, + dired: bool, } // Fields that can be removed or added to the long format @@ -610,6 +618,8 @@ fn extract_quoting_style(options: &clap::ArgMatches, show_control: bool) -> Quot QuotingStyle::C { quotes: quoting_style::Quotes::Double, } + } else if options.get_flag(options::DIRED) { + QuotingStyle::Literal { show_control } } else { // TODO: use environment variable if available QuotingStyle::Shell { @@ -954,6 +964,11 @@ impl Config { None }; + let dired = options.get_flag(options::DIRED); + if dired && format != Format::Long { + return Err(Box::new(LsError::ConflictingArgumentDired())); + } + let dereference = if options.get_flag(options::dereference::ALL) { Dereference::All } else if options.get_flag(options::dereference::ARGS) { @@ -1003,6 +1018,7 @@ impl Config { }, group_directories_first: options.get_flag(options::GROUP_DIRECTORIES_FIRST), line_ending: LineEnding::from_zero_flag(options.get_flag(options::ZERO)), + dired, }) } } @@ -1135,7 +1151,7 @@ pub fn uu_app() -> Command { Arg::new(options::DIRED) .long(options::DIRED) .short('D') - .hide(true) + .help("generate output designed for Emacs' dired (Directory Editor) mode") .action(ArgAction::SetTrue), ) // The next four arguments do not override with the other format @@ -1844,6 +1860,7 @@ pub fn list(locs: Vec<&Path>, config: &Config) -> UResult<()> { let mut files = Vec::::new(); let mut dirs = Vec::::new(); let mut out = BufWriter::new(stdout()); + let mut dired = DiredOutput::default(); let initial_locs_len = locs.len(); for loc in locs { @@ -1877,7 +1894,7 @@ pub fn list(locs: Vec<&Path>, config: &Config) -> UResult<()> { sort_entries(&mut files, config, &mut out); sort_entries(&mut dirs, config, &mut out); - display_items(&files, config, &mut out)?; + display_items(&files, config, &mut out, &mut dired)?; for (pos, path_data) in dirs.iter().enumerate() { // Do read_dir call here to match GNU semantics by printing @@ -1899,7 +1916,13 @@ pub fn list(locs: Vec<&Path>, config: &Config) -> UResult<()> { // Print dir heading - name... 'total' comes after error display if initial_locs_len > 1 || config.recursive { if pos.eq(&0usize) && files.is_empty() { + if config.dired { + dired::indent(&mut out)?; + } writeln!(out, "{}:", path_data.p_buf.display())?; + if config.dired { + dired::calculate_offset_and_push(&mut dired, path_data.display_name.len()); + } } else { writeln!(out, "\n{}:", path_data.p_buf.display())?; } @@ -1909,9 +1932,18 @@ pub fn list(locs: Vec<&Path>, config: &Config) -> UResult<()> { &path_data.p_buf, path_data.must_dereference, )?); - enter_directory(path_data, read_dir, config, &mut out, &mut listed_ancestors)?; + enter_directory( + path_data, + read_dir, + config, + &mut out, + &mut listed_ancestors, + &mut dired, + )?; + } + if config.dired { + dired::print_dired_output(config, &dired, &mut out)?; } - Ok(()) } @@ -2022,6 +2054,7 @@ fn enter_directory( config: &Config, out: &mut BufWriter, listed_ancestors: &mut HashSet, + dired: &mut DiredOutput, ) -> UResult<()> { // Create vec of entries with initial dot files let mut entries: Vec = if config.files == Files::All { @@ -2067,10 +2100,14 @@ fn enter_directory( // Print total after any error display if config.format == Format::Long || config.alloc_size { - display_total(&entries, config, out)?; + let total = return_total(&entries, config, out)?; + write!(out, "{}", total.as_str())?; + if config.dired { + dired::add_total(total.len(), dired); + } } - display_items(&entries, config, out)?; + display_items(&entries, config, out, dired)?; if config.recursive { for e in entries @@ -2095,7 +2132,7 @@ fn enter_directory( .insert(FileInformation::from_path(&e.p_buf, e.must_dereference)?) { writeln!(out, "\n{}:", e.p_buf.display())?; - enter_directory(e, rd, config, out, listed_ancestors)?; + enter_directory(e, rd, config, out, listed_ancestors, dired)?; listed_ancestors .remove(&FileInformation::from_path(&e.p_buf, e.must_dereference)?); } else { @@ -2154,7 +2191,11 @@ fn pad_right(string: &str, count: usize) -> String { format!("{string:) -> UResult<()> { +fn return_total( + items: &[PathData], + config: &Config, + out: &mut BufWriter, +) -> UResult { let mut total_size = 0; for item in items { total_size += item @@ -2162,13 +2203,14 @@ fn display_total(items: &[PathData], config: &Config, out: &mut BufWriter) -> UResult<()> { +fn display_items( + items: &[PathData], + config: &Config, + out: &mut BufWriter, + dired: &mut DiredOutput, +) -> UResult<()> { // `-Z`, `--context`: // Display the SELinux security context or '?' if none is found. When used with the `-l` // option, print the security context to the left of the size column. @@ -2220,6 +2267,7 @@ fn display_items(items: &[PathData], config: &Config, out: &mut BufWriter, + dired: &mut DiredOutput, ) -> UResult<()> { + let mut output_display: String = String::new(); + if config.dired { + output_display += " "; + } if let Some(md) = item.md(out) { write!( - out, + output_display, "{}{} {}", display_permissions(md, true), if item.security_context.len() > 1 { @@ -2416,49 +2469,54 @@ fn display_item_long( "" }, pad_left(&display_symlink_count(md), padding.link_count) - )?; + ) + .unwrap(); if config.long.owner { write!( - out, + output_display, " {}", pad_right(&display_uname(md, config), padding.uname) - )?; + ) + .unwrap(); } if config.long.group { write!( - out, + output_display, " {}", pad_right(&display_group(md, config), padding.group) - )?; + ) + .unwrap(); } if config.context { write!( - out, + output_display, " {}", pad_right(&item.security_context, padding.context) - )?; + ) + .unwrap(); } // Author is only different from owner on GNU/Hurd, so we reuse // the owner, since GNU/Hurd is not currently supported by Rust. if config.long.author { write!( - out, + output_display, " {}", pad_right(&display_uname(md, config), padding.uname) - )?; + ) + .unwrap(); } match display_len_or_rdev(md, config) { SizeOrDeviceId::Size(size) => { - write!(out, " {}", pad_left(&size, padding.size))?; + write!(output_display, " {}", pad_left(&size, padding.size)).unwrap(); } SizeOrDeviceId::Device(major, minor) => { write!( - out, + output_display, " {}, {}", pad_left( &major, @@ -2478,19 +2536,23 @@ fn display_item_long( #[cfg(unix)] padding.minor, ), - )?; + ) + .unwrap(); } }; - let displayed_file = display_file_name(item, config, None, String::new(), out).contents; + write!(output_display, " {} ", display_date(md, config)).unwrap(); - write!( - out, - " {} {}{}", - display_date(md, config), - displayed_file, - config.line_ending - )?; + let displayed_file = display_file_name(item, config, None, String::new(), out).contents; + if config.dired { + let (start, end) = dired::calculate_dired_byte_positions( + output_display.len(), + displayed_file.len(), + &dired.dired_positions, + ); + dired::update_positions(start, end, dired, false); + } + write!(output_display, "{}{}", displayed_file, config.line_ending).unwrap(); } else { #[cfg(unix)] let leading_char = { @@ -2526,7 +2588,7 @@ fn display_item_long( }; write!( - out, + output_display, "{}{} {}", format_args!("{leading_char}?????????"), if item.security_context.len() > 1 { @@ -2537,41 +2599,53 @@ fn display_item_long( "" }, pad_left("?", padding.link_count) - )?; + ) + .unwrap(); if config.long.owner { - write!(out, " {}", pad_right("?", padding.uname))?; + write!(output_display, " {}", pad_right("?", padding.uname)).unwrap(); } if config.long.group { - write!(out, " {}", pad_right("?", padding.group))?; + write!(output_display, " {}", pad_right("?", padding.group)).unwrap(); } if config.context { write!( - out, + output_display, " {}", pad_right(&item.security_context, padding.context) - )?; + ) + .unwrap(); } // Author is only different from owner on GNU/Hurd, so we reuse // the owner, since GNU/Hurd is not currently supported by Rust. if config.long.author { - write!(out, " {}", pad_right("?", padding.uname))?; + write!(output_display, " {}", pad_right("?", padding.uname)).unwrap(); } let displayed_file = display_file_name(item, config, None, String::new(), out).contents; let date_len = 12; - writeln!( - out, - " {} {} {}", + write!( + output_display, + " {} {} ", pad_left("?", padding.size), pad_left("?", date_len), - displayed_file, - )?; + ) + .unwrap(); + + if config.dired { + dired::calculate_and_update_positions( + output_display.len(), + displayed_file.trim().len(), + dired, + ); + } + write!(output_display, "{}{}", displayed_file, config.line_ending).unwrap(); } + write!(out, "{}", output_display)?; Ok(()) } diff --git a/src/uucore/src/lib/features/quoting_style.rs b/src/uucore/src/lib/features/quoting_style.rs index 1b9a76aae22..b517dbd8db5 100644 --- a/src/uucore/src/lib/features/quoting_style.rs +++ b/src/uucore/src/lib/features/quoting_style.rs @@ -4,12 +4,14 @@ // file that was distributed with this source code. use std::char::from_digit; use std::ffi::OsStr; +use std::fmt; // These are characters with special meaning in the shell (e.g. bash). // The first const contains characters that only have a special meaning when they appear at the beginning of a name. const SPECIAL_SHELL_CHARS_START: &[char] = &['~', '#']; const SPECIAL_SHELL_CHARS: &str = "`$&*()|[]{};\\'\"<>?! "; +#[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum QuotingStyle { Shell { escape: bool, @@ -24,7 +26,7 @@ pub enum QuotingStyle { }, } -#[derive(Clone, Copy)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum Quotes { None, Single, @@ -316,6 +318,42 @@ pub fn escape_name(name: &OsStr, style: &QuotingStyle) -> String { } } +impl fmt::Display for QuotingStyle { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match *self { + Self::Shell { + escape, + always_quote, + show_control, + } => { + let mut style = "shell".to_string(); + if escape { + style.push_str("-escape"); + } + if always_quote { + style.push_str("-always-quote"); + } + if show_control { + style.push_str("-show-control"); + } + f.write_str(&style) + } + Self::C { .. } => f.write_str("C"), + Self::Literal { .. } => f.write_str("literal"), + } + } +} + +impl fmt::Display for Quotes { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match *self { + Self::None => f.write_str("None"), + Self::Single => f.write_str("Single"), + Self::Double => f.write_str("Double"), + } + } +} + #[cfg(test)] mod tests { use crate::quoting_style::{escape_name, Quotes, QuotingStyle}; @@ -732,4 +770,45 @@ mod tests { ], ); } + + #[test] + fn test_quoting_style_display() { + let style = QuotingStyle::Shell { + escape: true, + always_quote: false, + show_control: false, + }; + assert_eq!(format!("{}", style), "shell-escape"); + + let style = QuotingStyle::Shell { + escape: false, + always_quote: true, + show_control: false, + }; + assert_eq!(format!("{}", style), "shell-always-quote"); + + let style = QuotingStyle::Shell { + escape: false, + always_quote: false, + show_control: true, + }; + assert_eq!(format!("{}", style), "shell-show-control"); + + let style = QuotingStyle::C { + quotes: Quotes::Double, + }; + assert_eq!(format!("{}", style), "C"); + + let style = QuotingStyle::Literal { + show_control: false, + }; + assert_eq!(format!("{}", style), "literal"); + } + + #[test] + fn test_quotes_display() { + assert_eq!(format!("{}", Quotes::None), "None"); + assert_eq!(format!("{}", Quotes::Single), "Single"); + assert_eq!(format!("{}", Quotes::Double), "Double"); + } } diff --git a/tests/by-util/test_ls.rs b/tests/by-util/test_ls.rs index 5f2b7e44350..87b3066569a 100644 --- a/tests/by-util/test_ls.rs +++ b/tests/by-util/test_ls.rs @@ -2,7 +2,7 @@ // // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// spell-checker:ignore (words) READMECAREFULLY birthtime doesntexist oneline somebackup lrwx somefile somegroup somehiddenbackup somehiddenfile tabsize aaaaaaaa bbbb cccc dddddddd ncccc neee naaaaa nbcdef nfffff +// spell-checker:ignore (words) READMECAREFULLY birthtime doesntexist oneline somebackup lrwx somefile somegroup somehiddenbackup somehiddenfile tabsize aaaaaaaa bbbb cccc dddddddd ncccc neee naaaaa nbcdef nfffff dired subdired #[cfg(any(unix, feature = "feat_selinux"))] use crate::common::util::expected_result; @@ -3522,3 +3522,126 @@ fn test_ls_perm_io_errors() { .code_is(1) .stderr_contains("Permission denied"); } + +#[test] +fn test_ls_dired_incompatible() { + let scene = TestScenario::new(util_name!()); + + scene + .ucmd() + .arg("--dired") + .fails() + .code_is(1) + .stderr_contains("--dired requires --format=long"); +} + +#[test] +fn test_ls_dired_recursive() { + let scene = TestScenario::new(util_name!()); + + scene + .ucmd() + .arg("--dired") + .arg("-l") + .arg("-R") + .succeeds() + .stdout_does_not_contain("//DIRED//") + .stdout_contains(" total 0") + .stdout_contains("//SUBDIRED// 2 3") + .stdout_contains("//DIRED-OPTIONS// --quoting-style"); +} + +#[test] +fn test_ls_dired_simple() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + scene + .ucmd() + .arg("--dired") + .arg("-l") + .succeeds() + .stdout_contains(" total 0"); + + at.mkdir("d"); + at.touch("d/a1"); + let mut cmd = scene.ucmd(); + cmd.arg("--dired").arg("-l").arg("d"); + let result = cmd.succeeds(); + result.stdout_contains(" total 0"); + println!(" result.stdout = {:#?}", result.stdout_str()); + + let dired_line = result + .stdout_str() + .lines() + .find(|&line| line.starts_with("//DIRED//")) + .unwrap(); + let positions: Vec = dired_line + .split_whitespace() + .skip(1) + .map(|s| s.parse().unwrap()) + .collect(); + + assert_eq!(positions.len(), 2); + + let start_pos = positions[0]; + let end_pos = positions[1]; + + // Extract the filename using the positions + let filename = + String::from_utf8(result.stdout_str().as_bytes()[start_pos..end_pos].to_vec()).unwrap(); + + assert_eq!(filename, "a1"); +} + +#[test] +fn test_ls_dired_complex() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + at.mkdir("d"); + at.mkdir("d/d"); + at.touch("d/a1"); + at.touch("d/a22"); + at.touch("d/a333"); + at.touch("d/a4444"); + + let mut cmd = scene.ucmd(); + cmd.arg("--dired").arg("-l").arg("d"); + let result = cmd.succeeds(); + // Number of blocks + #[cfg(target_os = "linux")] + result.stdout_contains(" total 4"); + + let output = result.stdout_str().to_string(); + println!("Output:\n{}", output); + + let dired_line = output + .lines() + .find(|&line| line.starts_with("//DIRED//")) + .unwrap(); + let positions: Vec = dired_line + .split_whitespace() + .skip(1) + .map(|s| s.parse().unwrap()) + .collect(); + println!("{:?}", positions); + println!("Parsed byte positions: {:?}", positions); + assert_eq!(positions.len() % 2, 0); // Ensure there's an even number of positions + + let filenames: Vec = positions + .chunks(2) + .map(|chunk| { + let start_pos = chunk[0]; + let end_pos = chunk[1]; + let filename = String::from_utf8(output.as_bytes()[start_pos..=end_pos].to_vec()) + .unwrap() + .trim() + .to_string(); + println!("Extracted filename: {}", filename); + filename + }) + .collect(); + + println!("Extracted filenames: {:?}", filenames); + assert_eq!(filenames, vec!["a1", "a22", "a333", "a4444", "d"]); +} diff --git a/util/build-gnu.sh b/util/build-gnu.sh index 2d949bbb333..75be52587dd 100755 --- a/util/build-gnu.sh +++ b/util/build-gnu.sh @@ -3,7 +3,7 @@ # # UU_MAKE_PROFILE == 'debug' | 'release' ## build profile for *uutils* build; may be supplied by caller, defaults to 'release' -# spell-checker:ignore (paths) abmon deref discrim eacces getlimits getopt ginstall inacc infloop inotify reflink ; (misc) INT_OFLOW OFLOW baddecode submodules ; (vars/env) SRCDIR vdir rcexp xpart +# spell-checker:ignore (paths) abmon deref discrim eacces getlimits getopt ginstall inacc infloop inotify reflink ; (misc) INT_OFLOW OFLOW baddecode submodules ; (vars/env) SRCDIR vdir rcexp xpart dired set -e @@ -261,6 +261,10 @@ sed -i -e "s/Try 'mv --help' for more information/For more information, try '--h # disable these test cases sed -i -E "s|^([^#]*2_31.*)$|#\1|g" tests/printf/printf-cov.pl + +# with ls --dired, in case of error, we have a slightly different error position +sed -i -e "s|44 45|47 48|" tests/ls/stat-failed.sh + sed -i -e "s/du: invalid -t argument/du: invalid --threshold argument/" -e "s/du: option requires an argument/error: a value is required for '--threshold ' but none was supplied/" -e "/Try 'du --help' for more information./d" tests/du/threshold.sh # disable two kind of tests: From 89aef112088f14372ba8e6e9226c25bb3034adff Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Wed, 20 Sep 2023 10:02:20 +0200 Subject: [PATCH 0097/2851] build-gnu.sh: fix formatting --- util/build-gnu.sh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/util/build-gnu.sh b/util/build-gnu.sh index 75be52587dd..521f2208e8e 100755 --- a/util/build-gnu.sh +++ b/util/build-gnu.sh @@ -261,9 +261,8 @@ sed -i -e "s/Try 'mv --help' for more information/For more information, try '--h # disable these test cases sed -i -E "s|^([^#]*2_31.*)$|#\1|g" tests/printf/printf-cov.pl - # with ls --dired, in case of error, we have a slightly different error position -sed -i -e "s|44 45|47 48|" tests/ls/stat-failed.sh +sed -i -e "s|44 45|47 48|" tests/ls/stat-failed.sh sed -i -e "s/du: invalid -t argument/du: invalid --threshold argument/" -e "s/du: option requires an argument/error: a value is required for '--threshold ' but none was supplied/" -e "/Try 'du --help' for more information./d" tests/du/threshold.sh From 5eb1fd0b024e23598925ef954f169f401473d5f4 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Wed, 20 Sep 2023 10:36:32 +0200 Subject: [PATCH 0098/2851] ci: remove committing from CheckScripts.yml --- .github/workflows/CheckScripts.yml | 20 +------------------- 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/.github/workflows/CheckScripts.yml b/.github/workflows/CheckScripts.yml index 995b5456f18..c18c4733cbe 100644 --- a/.github/workflows/CheckScripts.yml +++ b/.github/workflows/CheckScripts.yml @@ -41,14 +41,9 @@ jobs: shell_fmt: name: ShellScript/Format - # no need to run in pr events - # shfmt will be performed on main branch when the PR is merged - if: github.event_name != 'pull_request' runs-on: ubuntu-latest - needs: [ shell_check ] permissions: - contents: write - pull-requests: write + contents: read steps: - uses: actions/checkout@v4 - name: Setup shfmt @@ -56,19 +51,6 @@ jobs: - name: Run shfmt shell: bash run: | - # show differs first for every files that need to be formatted # fmt options: bash syntax, 4 spaces indent, indent for switch-case echo "## show the differences between formatted and original scripts..." find ${{ env.SCRIPT_DIR }} -name "*.sh" -print0 | xargs -0 shfmt -ln=bash -i 4 -ci -d || true - # perform a shell format - echo "## perform a shell format..." - # ignore the error code because `-d` will always return false when the file has difference - find ${{ env.SCRIPT_DIR }} -name "*.sh" -print0 | xargs -0 shfmt -ln=bash -i 4 -ci -w - - name: Commit any changes - uses: EndBug/add-and-commit@v9 - with: - default_author: github_actions - message: "style: auto format by CI (shfmt)" - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - From 3bb180f4711dbbe56dfd3438e3165742a524630d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 20 Sep 2023 16:33:12 +0000 Subject: [PATCH 0099/2851] chore(deps): update rust crate winapi-util to 0.1.6 --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ece92cdfe46..d4340ff961c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3429,9 +3429,9 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" dependencies = [ "winapi", ] diff --git a/Cargo.toml b/Cargo.toml index 866b1f58549..42949b7c23c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -328,7 +328,7 @@ unicode-segmentation = "1.10.1" unicode-width = "0.1.11" utf-8 = "0.7.6" walkdir = "2.4" -winapi-util = "0.1.5" +winapi-util = "0.1.6" windows-sys = { version = "0.48.0", default-features = false } xattr = "1.0.1" zip = { version = "0.6.6", default_features = false, features = ["deflate"] } From 2ee4006bfdfbdab521cd56f24112a9157295bae5 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 20 Sep 2023 23:08:42 +0000 Subject: [PATCH 0100/2851] chore(deps): update rust crate rayon to 1.8 --- Cargo.lock | 20 ++++---------------- Cargo.toml | 2 +- 2 files changed, 5 insertions(+), 17 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ece92cdfe46..635b6ce11b7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1413,16 +1413,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "num_cpus" -version = "1.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" -dependencies = [ - "hermit-abi", - "libc", -] - [[package]] name = "num_threads" version = "0.1.6" @@ -1707,9 +1697,9 @@ dependencies = [ [[package]] name = "rayon" -version = "1.7.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b" +checksum = "9c27db03db7734835b3f53954b534c91069375ce6ccaa2e065441e07d9b6cdb1" dependencies = [ "either", "rayon-core", @@ -1717,14 +1707,12 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d" +checksum = "5ce3fb6ad83f861aac485e76e1985cd109d9a3713802152be56c3b1f0e0658ed" dependencies = [ - "crossbeam-channel", "crossbeam-deque", "crossbeam-utils", - "num_cpus", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 866b1f58549..d8eca9f3a10 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -308,7 +308,7 @@ platform-info = "2.0.2" quick-error = "2.0.1" rand = { version = "0.8", features = ["small_rng"] } rand_core = "0.6" -rayon = "1.7" +rayon = "1.8" redox_syscall = "0.4" regex = "1.9.5" rstest = "0.18.2" From 78e774a937fd83faeef676e1fbfea934c877339c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 21 Sep 2023 04:49:34 +0000 Subject: [PATCH 0101/2851] chore(deps): update rust crate blake3 to 1.5.0 --- Cargo.lock | 16 ++++------------ Cargo.toml | 2 +- 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ece92cdfe46..44a85a20822 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -88,9 +88,9 @@ checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" [[package]] name = "arrayvec" -version = "0.7.2" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" [[package]] name = "autocfg" @@ -166,16 +166,15 @@ dependencies = [ [[package]] name = "blake3" -version = "1.4.1" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "199c42ab6972d92c9f8995f086273d25c42fc0f7b2a1fcefba465c1352d25ba5" +checksum = "0231f06152bf547e9c2b5194f247cd97aacf6dcd8b15d8e5ec0663f64580da87" dependencies = [ "arrayref", "arrayvec", "cc", "cfg-if", "constant_time_eq", - "digest", ] [[package]] @@ -732,7 +731,6 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", "crypto-common", - "subtle", ] [[package]] @@ -2075,12 +2073,6 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" -[[package]] -name = "subtle" -version = "2.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" - [[package]] name = "syn" version = "1.0.109" diff --git a/Cargo.toml b/Cargo.toml index 866b1f58549..b7ca08b9483 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -339,7 +339,7 @@ sha1 = "0.10.5" sha2 = "0.10.7" sha3 = "0.10.8" blake2b_simd = "1.0.2" -blake3 = "1.4.1" +blake3 = "1.5.0" sm3 = "0.4.2" digest = "0.10.7" From 23ee9b622d19535da485346eaf9ff2d51740ff5f Mon Sep 17 00:00:00 2001 From: pin <90570748+0323pin@users.noreply.github.com> Date: Thu, 21 Sep 2023 12:24:08 +0200 Subject: [PATCH 0102/2851] Add NetBSD support to uucore. (#5289) * Add NetBSD support to uucore. Fixes https://github.com/uutils/coreutils/issues/5288 --- src/uucore/src/lib/features/fs.rs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/uucore/src/lib/features/fs.rs b/src/uucore/src/lib/features/fs.rs index 3def63ad6b1..719efc7a6e7 100644 --- a/src/uucore/src/lib/features/fs.rs +++ b/src/uucore/src/lib/features/fs.rs @@ -114,6 +114,7 @@ impl FileInformation { not(target_vendor = "apple"), not(target_os = "android"), not(target_os = "freebsd"), + not(target_os = "netbsd"), not(target_arch = "aarch64"), not(target_arch = "riscv64"), target_pointer_width = "64" @@ -125,6 +126,7 @@ impl FileInformation { target_vendor = "apple", target_os = "android", target_os = "freebsd", + target_os = "netbsd", target_arch = "aarch64", target_arch = "riscv64", not(target_pointer_width = "64") @@ -137,9 +139,16 @@ impl FileInformation { #[cfg(unix)] pub fn inode(&self) -> u64 { - #[cfg(all(not(target_os = "freebsd"), target_pointer_width = "64"))] + #[cfg(all( + not(any(target_os = "freebsd", target_os = "netbsd")), + target_pointer_width = "64" + ))] return self.0.st_ino; - #[cfg(any(target_os = "freebsd", not(target_pointer_width = "64")))] + #[cfg(any( + target_os = "freebsd", + target_os = "netbsd", + not(target_pointer_width = "64") + ))] return self.0.st_ino.into(); } } From df211cd45f6a052054f761d56f0efe9bddd5068e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 21 Sep 2023 16:11:20 +0000 Subject: [PATCH 0103/2851] chore(deps): update rust crate sha1 to 0.10.6 --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 665df51207f..58a4324a6a1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1943,9 +1943,9 @@ checksum = "d193d69bae983fc11a79df82342761dfbf28a99fc8d203dca4c3c1b590948965" [[package]] name = "sha1" -version = "0.10.5" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if", "cpufeatures", diff --git a/Cargo.toml b/Cargo.toml index fd5d40999d9..d0c012f4751 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -335,7 +335,7 @@ zip = { version = "0.6.6", default_features = false, features = ["deflate"] } hex = "0.4.3" md-5 = "0.10.5" -sha1 = "0.10.5" +sha1 = "0.10.6" sha2 = "0.10.7" sha3 = "0.10.8" blake2b_simd = "1.0.2" From 1d10e0c6748a5b98a1efed6d0a39c49aafbc52fb Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Wed, 20 Sep 2023 22:00:30 +0200 Subject: [PATCH 0104/2851] ls: rename the function for something more explicit --- src/uu/ls/src/dired.rs | 2 +- src/uu/ls/src/ls.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/uu/ls/src/dired.rs b/src/uu/ls/src/dired.rs index f66d22b38dd..ba8f0933596 100644 --- a/src/uu/ls/src/dired.rs +++ b/src/uu/ls/src/dired.rs @@ -54,7 +54,7 @@ pub fn indent(out: &mut BufWriter) -> UResult<()> { Ok(()) } -pub fn calculate_offset_and_push(dired: &mut DiredOutput, path_len: usize) { +pub fn calculate_subdired(dired: &mut DiredOutput, path_len: usize) { let offset = if dired.subdired_positions.is_empty() { DIRED_TRAILING_OFFSET } else { diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 3d67cccaad1..7f977da2a2a 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -1921,7 +1921,7 @@ pub fn list(locs: Vec<&Path>, config: &Config) -> UResult<()> { } writeln!(out, "{}:", path_data.p_buf.display())?; if config.dired { - dired::calculate_offset_and_push(&mut dired, path_data.display_name.len()); + dired::calculate_subdired(&mut dired, path_data.display_name.len()); } } else { writeln!(out, "\n{}:", path_data.p_buf.display())?; From 8db6146dd3c6b131723bfd0cd3bf3b443a3efdc4 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Wed, 20 Sep 2023 22:00:38 +0200 Subject: [PATCH 0105/2851] ls remove old comment --- src/uu/ls/src/dired.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/uu/ls/src/dired.rs b/src/uu/ls/src/dired.rs index ba8f0933596..8e85a2e0fa8 100644 --- a/src/uu/ls/src/dired.rs +++ b/src/uu/ls/src/dired.rs @@ -95,7 +95,6 @@ pub fn add_total(total_len: usize, dired: &mut DiredOutput) { dired.just_printed_total = true; dired.dired_positions.push(BytePosition { start: 0, - // the 2 is from the trailing spaces // the 1 is from the line ending (\n) end: total_len + DIRED_TRAILING_OFFSET - 1, }); From a12dd2ee1eddb42d23646ef7964b919a4b752d3c Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Wed, 20 Sep 2023 22:09:34 +0200 Subject: [PATCH 0106/2851] ls rename the function for consistency --- src/uu/ls/src/dired.rs | 7 +++---- src/uu/ls/src/ls.rs | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/uu/ls/src/dired.rs b/src/uu/ls/src/dired.rs index 8e85a2e0fa8..288d7fc76dc 100644 --- a/src/uu/ls/src/dired.rs +++ b/src/uu/ls/src/dired.rs @@ -33,7 +33,7 @@ impl fmt::Display for BytePosition { static DIRED_TRAILING_OFFSET: usize = 2; /// Calculates the byte positions for DIRED -pub fn calculate_dired_byte_positions( +pub fn calculate_dired( output_display_len: usize, dfn_len: usize, dired_positions: &[BytePosition], @@ -143,12 +143,11 @@ mod tests { use super::*; #[test] - fn test_calculate_dired_byte_positions() { + fn test_calculate_dired() { let output_display = "sample_output".to_string(); let dfn = "sample_file".to_string(); let dired_positions = vec![BytePosition { start: 5, end: 10 }]; - let (start, end) = - calculate_dired_byte_positions(output_display.len(), dfn.len(), &dired_positions); + let (start, end) = calculate_dired(output_display.len(), dfn.len(), &dired_positions); assert_eq!(start, 24); assert_eq!(end, 35); diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 7f977da2a2a..f7a3b79ae6a 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -2545,7 +2545,7 @@ fn display_item_long( let displayed_file = display_file_name(item, config, None, String::new(), out).contents; if config.dired { - let (start, end) = dired::calculate_dired_byte_positions( + let (start, end) = dired::calculate_dired( output_display.len(), displayed_file.len(), &dired.dired_positions, From 0794d1338df62f303e7077e3397acd1873dc2766 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Fri, 22 Sep 2023 10:23:01 +0200 Subject: [PATCH 0107/2851] ls: fix test which fails if /tmp uses tmpfs --- tests/by-util/test_ls.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/tests/by-util/test_ls.rs b/tests/by-util/test_ls.rs index 87b3066569a..23dfafa3200 100644 --- a/tests/by-util/test_ls.rs +++ b/tests/by-util/test_ls.rs @@ -2,7 +2,7 @@ // // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// spell-checker:ignore (words) READMECAREFULLY birthtime doesntexist oneline somebackup lrwx somefile somegroup somehiddenbackup somehiddenfile tabsize aaaaaaaa bbbb cccc dddddddd ncccc neee naaaaa nbcdef nfffff dired subdired +// spell-checker:ignore (words) READMECAREFULLY birthtime doesntexist oneline somebackup lrwx somefile somegroup somehiddenbackup somehiddenfile tabsize aaaaaaaa bbbb cccc dddddddd ncccc neee naaaaa nbcdef nfffff dired subdired tmpfs #[cfg(any(unix, feature = "feat_selinux"))] use crate::common::util::expected_result; @@ -3608,9 +3608,13 @@ fn test_ls_dired_complex() { let mut cmd = scene.ucmd(); cmd.arg("--dired").arg("-l").arg("d"); let result = cmd.succeeds(); - // Number of blocks + + // Number of blocks. We run this test only if the default size of a newly created directory is + // 4096 bytes to prevent it from failing where this is not the case (e.g. using tmpfs for /tmp). #[cfg(target_os = "linux")] - result.stdout_contains(" total 4"); + if at.metadata("d/d").len() == 4096 { + result.stdout_contains(" total 4"); + } let output = result.stdout_str().to_string(); println!("Output:\n{}", output); From 38831c46d1b896bac7bd45af31644d2733be2c28 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Fri, 22 Sep 2023 10:58:12 +0200 Subject: [PATCH 0108/2851] relpath: show error if no argument provided Fixes #5300 --- src/uu/relpath/src/relpath.rs | 6 +++++- tests/by-util/test_relpath.rs | 7 +++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/uu/relpath/src/relpath.rs b/src/uu/relpath/src/relpath.rs index 46dd0d6631e..aa8b5caab96 100644 --- a/src/uu/relpath/src/relpath.rs +++ b/src/uu/relpath/src/relpath.rs @@ -82,6 +82,10 @@ pub fn uu_app() -> Command { .arg(Arg::new(options::DIR).short('d').help( "If any of FROM and TO is not subpath of DIR, output absolute path instead of relative", )) - .arg(Arg::new(options::TO).value_hint(clap::ValueHint::AnyPath)) + .arg( + Arg::new(options::TO) + .value_hint(clap::ValueHint::AnyPath) + .required(true), + ) .arg(Arg::new(options::FROM).value_hint(clap::ValueHint::AnyPath)) } diff --git a/tests/by-util/test_relpath.rs b/tests/by-util/test_relpath.rs index 8a3c91802e1..f506e01c5cc 100644 --- a/tests/by-util/test_relpath.rs +++ b/tests/by-util/test_relpath.rs @@ -180,3 +180,10 @@ fn test_relpath_no_from_with_d() { assert!(Path::new(&result_stdout).is_absolute()); } } + +#[test] +fn test_relpath_no_to() { + new_ucmd!() + .fails() + .stderr_contains("required arguments were not provided"); +} From cc6a1ae4f415e572733dbfbd9c56dc0d75f05061 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 22 Sep 2023 15:40:11 +0000 Subject: [PATCH 0109/2851] chore(deps): update rust crate md-5 to 0.10.6 --- Cargo.lock | 5 +++-- Cargo.toml | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 58a4324a6a1..3723c52da62 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1275,10 +1275,11 @@ checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" [[package]] name = "md-5" -version = "0.10.5" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6365506850d44bff6e2fbcb5176cf63650e48bd45ef2fe2665ae1570e0f4b9ca" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" dependencies = [ + "cfg-if", "digest", ] diff --git a/Cargo.toml b/Cargo.toml index d0c012f4751..65b23faa2bb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -334,7 +334,7 @@ xattr = "1.0.1" zip = { version = "0.6.6", default_features = false, features = ["deflate"] } hex = "0.4.3" -md-5 = "0.10.5" +md-5 = "0.10.6" sha1 = "0.10.6" sha2 = "0.10.7" sha3 = "0.10.8" From 75044c1bc47321d8b360045693dd386515e27fa9 Mon Sep 17 00:00:00 2001 From: KAA the Wise Date: Fri, 22 Sep 2023 18:41:31 +0300 Subject: [PATCH 0110/2851] rm: make option types public Made `Options` and `InteractiveMode` public and added documentation for them. --- src/uu/rm/src/rm.rs | 50 ++++++++++++++++++++++++++++++++++++--------- 1 file changed, 40 insertions(+), 10 deletions(-) diff --git a/src/uu/rm/src/rm.rs b/src/uu/rm/src/rm.rs index d9421d0ae71..96e0fa7aa2b 100644 --- a/src/uu/rm/src/rm.rs +++ b/src/uu/rm/src/rm.rs @@ -18,22 +18,52 @@ use uucore::{format_usage, help_about, help_section, help_usage, prompt_yes, sho use walkdir::{DirEntry, WalkDir}; #[derive(Eq, PartialEq, Clone, Copy)] -enum InteractiveMode { +/// Enum, determining when the `rm` will prompt the user about the file deletion +pub enum InteractiveMode { + /// Never prompt Never, + /// Prompt once before removing more than three files, or when removing + /// recursively. Once, + /// Prompt before every removal Always, + /// TODO clarify what this option does PromptProtected, } -struct Options { - force: bool, - interactive: InteractiveMode, +/// Options for the `rm` command +/// +/// All options are public so that the options can be programmatically +/// constructed by other crates, such as Nushell. That means that this struct +/// is part of our public API. It should therefore not be changed without good +/// reason. +/// +/// The fields are documented with the arguments that determine their value. +pub struct Options { + /// `-f`, `--force` + pub force: bool, + /// Iterative mode, determines when the command will prompt. + /// + /// Set by the following arguments: + /// - `-i`: [`InteractiveMode::Always`] + /// - `-I`: [`InteractiveMode::Once`] + /// - `--interactive`: sets one of the above or [`InteractiveMode::Never`] + /// - `-f`: implicitly sets [`InteractiveMode::Never`] + /// + /// If no other option sets this mode, [`InteractiveMode::PromptProtected`] + /// is used + pub interactive: InteractiveMode, #[allow(dead_code)] - one_fs: bool, - preserve_root: bool, - recursive: bool, - dir: bool, - verbose: bool, + /// `--one-file-system` + pub one_fs: bool, + /// `--preserve-root`/`--no-preserve-root` + pub preserve_root: bool, + /// `-r`, `--recursive` + pub recursive: bool, + /// `-d`, `--dir` + pub dir: bool, + /// `-v`, `--verbose` + pub verbose: bool, } const ABOUT: &str = help_about!("rm.md"); @@ -268,7 +298,7 @@ fn remove(files: &[&OsStr], options: &Options) -> bool { // TODO: actually print out the specific error // TODO: When the error is not about missing files // (e.g., permission), even rm -f should fail with - // outputting the error, but there's no easy eay. + // outputting the error, but there's no easy way. if options.force { false } else { From 20fb473da852d9b09a592b258c4c183c3d97dc64 Mon Sep 17 00:00:00 2001 From: KAA the Wise Date: Fri, 22 Sep 2023 19:17:51 +0300 Subject: [PATCH 0111/2851] rm: make the `remove` function public --- src/uu/rm/src/rm.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/uu/rm/src/rm.rs b/src/uu/rm/src/rm.rs index 96e0fa7aa2b..19c749cf09b 100644 --- a/src/uu/rm/src/rm.rs +++ b/src/uu/rm/src/rm.rs @@ -279,7 +279,13 @@ pub fn uu_app() -> Command { } // TODO: implement one-file-system (this may get partially implemented in walkdir) -fn remove(files: &[&OsStr], options: &Options) -> bool { +/// Remove (or unlink) the given files +/// +/// Returns true if it has encountered an error. +/// +/// Behavior is determined by the `options` parameter, see [`Options`] for +/// details. +pub fn remove(files: &[&OsStr], options: &Options) -> bool { let mut had_err = false; for filename in files { From 30f1fceddcd77c79ec505b4dfad9b28e4d786d18 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sat, 23 Sep 2023 09:29:00 +0200 Subject: [PATCH 0112/2851] add nushell to the list of ignored names --- .vscode/cspell.dictionaries/acronyms+names.wordlist.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/.vscode/cspell.dictionaries/acronyms+names.wordlist.txt b/.vscode/cspell.dictionaries/acronyms+names.wordlist.txt index 8711913d99d..c004ea2f822 100644 --- a/.vscode/cspell.dictionaries/acronyms+names.wordlist.txt +++ b/.vscode/cspell.dictionaries/acronyms+names.wordlist.txt @@ -58,6 +58,7 @@ MinGW Minix NetBSD Novell +Nushell OpenBSD POSIX PowerPC From 48613b47176c30c414f979edd5350d7c1aa56ba6 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Fri, 22 Sep 2023 22:12:44 +0200 Subject: [PATCH 0113/2851] ls --dired: replace the previous "total: xx" padding method by something easier --- src/uu/ls/src/dired.rs | 47 ++++++++++++++++-------------------------- src/uu/ls/src/ls.rs | 2 +- util/build-gnu.sh | 6 +++--- 3 files changed, 22 insertions(+), 33 deletions(-) diff --git a/src/uu/ls/src/dired.rs b/src/uu/ls/src/dired.rs index 288d7fc76dc..8574e750e2d 100644 --- a/src/uu/ls/src/dired.rs +++ b/src/uu/ls/src/dired.rs @@ -20,7 +20,7 @@ pub struct BytePosition { pub struct DiredOutput { pub dired_positions: Vec, pub subdired_positions: Vec, - pub just_printed_total: bool, + pub padding: usize, } impl fmt::Display for BytePosition { @@ -75,7 +75,7 @@ pub fn print_dired_output( out.flush()?; if config.recursive { print_positions("//SUBDIRED//", &dired.subdired_positions); - } else if !dired.just_printed_total { + } else if dired.padding == 0 { print_positions("//DIRED//", &dired.dired_positions); } println!("//DIRED-OPTIONS// --quoting-style={}", config.quoting_style); @@ -92,12 +92,9 @@ fn print_positions(prefix: &str, positions: &Vec) { } pub fn add_total(total_len: usize, dired: &mut DiredOutput) { - dired.just_printed_total = true; - dired.dired_positions.push(BytePosition { - start: 0, - // the 1 is from the line ending (\n) - end: total_len + DIRED_TRAILING_OFFSET - 1, - }); + // when dealing with " total: xx", it isn't part of the //DIRED// + // so, we just keep the size line to add it to the position of the next file + dired.padding = total_len + DIRED_TRAILING_OFFSET; } /// Calculates byte positions and updates the dired structure. @@ -114,28 +111,20 @@ pub fn calculate_and_update_positions( }); let start = output_display_len + offset + DIRED_TRAILING_OFFSET; let end = start + dfn_len; - update_positions(start, end, dired, true); + update_positions(start, end, dired); } /// Updates the dired positions based on the given start and end positions. -/// update when it is the first element in the list (to manage "total X" +/// update when it is the first element in the list (to manage "total X") /// insert when it isn't the about total -pub fn update_positions(start: usize, end: usize, dired: &mut DiredOutput, adjust: bool) { - if dired.just_printed_total { - if let Some(last_position) = dired.dired_positions.last_mut() { - *last_position = BytePosition { - start: if adjust { - start + last_position.end - } else { - start - }, - end: if adjust { end + last_position.end } else { end }, - }; - dired.just_printed_total = false; - } - } else { - dired.dired_positions.push(BytePosition { start, end }); - } +pub fn update_positions(start: usize, end: usize, dired: &mut DiredOutput) { + // padding can be 0 but as it doesn't matter< + dired.dired_positions.push(BytePosition { + start: start + dired.padding, + end: end + dired.padding, + }); + // Remove the previous padding + dired.padding = 0; } #[cfg(test)] @@ -158,17 +147,17 @@ mod tests { let mut dired = DiredOutput { dired_positions: vec![BytePosition { start: 5, end: 10 }], subdired_positions: vec![], - just_printed_total: true, + padding: 10, }; // Test with adjust = true - update_positions(15, 20, &mut dired, true); + update_positions(15, 20, &mut dired); let last_position = dired.dired_positions.last().unwrap(); assert_eq!(last_position.start, 25); // 15 + 10 (end of the previous position) assert_eq!(last_position.end, 30); // 20 + 10 (end of the previous position) // Test with adjust = false - update_positions(30, 35, &mut dired, false); + update_positions(30, 35, &mut dired); let last_position = dired.dired_positions.last().unwrap(); assert_eq!(last_position.start, 30); assert_eq!(last_position.end, 35); diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index f7a3b79ae6a..301aefaf3a3 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -2550,7 +2550,7 @@ fn display_item_long( displayed_file.len(), &dired.dired_positions, ); - dired::update_positions(start, end, dired, false); + dired::update_positions(start, end, dired); } write!(output_display, "{}{}", displayed_file, config.line_ending).unwrap(); } else { diff --git a/util/build-gnu.sh b/util/build-gnu.sh index 521f2208e8e..e2c67539441 100755 --- a/util/build-gnu.sh +++ b/util/build-gnu.sh @@ -261,11 +261,11 @@ sed -i -e "s/Try 'mv --help' for more information/For more information, try '--h # disable these test cases sed -i -E "s|^([^#]*2_31.*)$|#\1|g" tests/printf/printf-cov.pl -# with ls --dired, in case of error, we have a slightly different error position -sed -i -e "s|44 45|47 48|" tests/ls/stat-failed.sh - sed -i -e "s/du: invalid -t argument/du: invalid --threshold argument/" -e "s/du: option requires an argument/error: a value is required for '--threshold ' but none was supplied/" -e "/Try 'du --help' for more information./d" tests/du/threshold.sh +# with ls --dired, in case of error, we have a slightly different error position +sed -i -e "s|44 45|48 49|" tests/ls/stat-failed.sh + # disable two kind of tests: # "hostid BEFORE --help" doesn't fail for GNU. we fail. we are probably doing better # "hostid BEFORE --help AFTER " same for this From 06219350fa4d9277b8a1adea1050c180db39ff32 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Sat, 23 Sep 2023 14:44:30 +0200 Subject: [PATCH 0114/2851] nl: fix output order if stdin and files are mixed --- src/uu/nl/src/nl.rs | 20 +++++++------------- tests/by-util/test_nl.rs | 13 +++++++++++++ 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/src/uu/nl/src/nl.rs b/src/uu/nl/src/nl.rs index fef9c030a2e..6e1cb683534 100644 --- a/src/uu/nl/src/nl.rs +++ b/src/uu/nl/src/nl.rs @@ -168,7 +168,6 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { )); } - let mut read_stdin = false; let files: Vec = match matches.get_many::(options::FILE) { Some(v) => v.clone().map(|v| v.to_owned()).collect(), None => vec!["-".to_owned()], @@ -178,21 +177,16 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { for file in &files { if file == "-" { - // If both file names and '-' are specified, we choose to treat first all - // regular files, and then read from stdin last. - read_stdin = true; - continue; + let mut buffer = BufReader::new(stdin()); + nl(&mut buffer, &mut stats, &settings)?; + } else { + let path = Path::new(file); + let reader = File::open(path).map_err_context(|| file.to_string())?; + let mut buffer = BufReader::new(reader); + nl(&mut buffer, &mut stats, &settings)?; } - let path = Path::new(file); - let reader = File::open(path).map_err_context(|| file.to_string())?; - let mut buffer = BufReader::new(reader); - nl(&mut buffer, &mut stats, &settings)?; } - if read_stdin { - let mut buffer = BufReader::new(stdin()); - nl(&mut buffer, &mut stats, &settings)?; - } Ok(()) } diff --git a/tests/by-util/test_nl.rs b/tests/by-util/test_nl.rs index b008c61de77..118c4cf047f 100644 --- a/tests/by-util/test_nl.rs +++ b/tests/by-util/test_nl.rs @@ -349,6 +349,19 @@ fn test_default_body_numbering_multiple_files() { .stdout_is(" 1\ta\n 2\tb\n 3\tc\n"); } +#[test] +fn test_default_body_numbering_multiple_files_and_stdin() { + let (at, mut ucmd) = at_and_ucmd!(); + + at.write("a.txt", "a"); + at.write("c.txt", "c"); + + ucmd.args(&["a.txt", "-", "c.txt"]) + .pipe_in("b") + .succeeds() + .stdout_is(" 1\ta\n 2\tb\n 3\tc\n"); +} + #[test] fn test_body_numbering_all_lines_without_delimiter() { for arg in ["-ba", "--body-numbering=a"] { From 4b76b2f33220b268f3ac148fa3ac0a110947b5a1 Mon Sep 17 00:00:00 2001 From: KAA the Wise Date: Sat, 23 Sep 2023 15:55:57 +0300 Subject: [PATCH 0115/2851] rm: document PromptProtected interactive mode option --- src/uu/rm/src/rm.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/rm/src/rm.rs b/src/uu/rm/src/rm.rs index 19c749cf09b..87767b904bd 100644 --- a/src/uu/rm/src/rm.rs +++ b/src/uu/rm/src/rm.rs @@ -27,7 +27,7 @@ pub enum InteractiveMode { Once, /// Prompt before every removal Always, - /// TODO clarify what this option does + /// Prompt only on write-protected files PromptProtected, } From 306a58a73147cd35491a3bd9facb6e0ee057752e Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Tue, 29 Nov 2022 10:20:04 +0100 Subject: [PATCH 0116/2851] tail: remove unused var "_event_counter" --- src/uu/tail/src/follow/watch.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/uu/tail/src/follow/watch.rs b/src/uu/tail/src/follow/watch.rs index 3ecb47f6734..cd1dd49c367 100644 --- a/src/uu/tail/src/follow/watch.rs +++ b/src/uu/tail/src/follow/watch.rs @@ -479,7 +479,6 @@ pub fn follow(mut observer: Observer, settings: &Settings) -> UResult<()> { let mut process = platform::ProcessChecker::new(observer.pid); - let mut _event_counter = 0; let mut _timeout_counter = 0; // main follow loop @@ -529,7 +528,6 @@ pub fn follow(mut observer: Observer, settings: &Settings) -> UResult<()> { .receiver .recv_timeout(settings.sleep_sec); if rx_result.is_ok() { - _event_counter += 1; _timeout_counter = 0; } From a4a69c8ee7e1b93d87500504800d91186d3972d6 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Tue, 29 Nov 2022 10:25:48 +0100 Subject: [PATCH 0117/2851] tail: "_timeout_counter" -> "timeout_counter" --- src/uu/tail/src/follow/watch.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/uu/tail/src/follow/watch.rs b/src/uu/tail/src/follow/watch.rs index cd1dd49c367..fbda27aa03d 100644 --- a/src/uu/tail/src/follow/watch.rs +++ b/src/uu/tail/src/follow/watch.rs @@ -479,7 +479,7 @@ pub fn follow(mut observer: Observer, settings: &Settings) -> UResult<()> { let mut process = platform::ProcessChecker::new(observer.pid); - let mut _timeout_counter = 0; + let mut timeout_counter = 0; // main follow loop loop { @@ -528,7 +528,7 @@ pub fn follow(mut observer: Observer, settings: &Settings) -> UResult<()> { .receiver .recv_timeout(settings.sleep_sec); if rx_result.is_ok() { - _timeout_counter = 0; + timeout_counter = 0; } let mut paths = vec![]; // Paths worth checking for new content to print @@ -567,7 +567,7 @@ pub fn follow(mut observer: Observer, settings: &Settings) -> UResult<()> { } Ok(Err(e)) => return Err(USimpleError::new(1, format!("NotifyError: {e}"))), Err(mpsc::RecvTimeoutError::Timeout) => { - _timeout_counter += 1; + timeout_counter += 1; } Err(e) => return Err(USimpleError::new(1, format!("RecvTimeoutError: {e}"))), } @@ -584,7 +584,7 @@ pub fn follow(mut observer: Observer, settings: &Settings) -> UResult<()> { _read_some = observer.files.tail_file(path, settings.verbose)?; } - if _timeout_counter == settings.max_unchanged_stats { + if timeout_counter == settings.max_unchanged_stats { /* TODO: [2021-10; jhscheer] implement timeout_counter for each file. ‘--max-unchanged-stats=n’ From 616c3f4a7fb199a525bcf148ed799516e13cf739 Mon Sep 17 00:00:00 2001 From: Benjamin Bara Date: Sat, 22 Jul 2023 11:25:39 +0200 Subject: [PATCH 0118/2851] util: extend run_ucmd_as_root for stdin/stdout This enables to give path to files (as str) which are then used as either stdin or stdout. --- tests/common/util.rs | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/tests/common/util.rs b/tests/common/util.rs index 0fbc58cd540..6f4e76d4228 100644 --- a/tests/common/util.rs +++ b/tests/common/util.rs @@ -2532,6 +2532,16 @@ pub fn expected_result(ts: &TestScenario, args: &[&str]) -> std::result::Result< pub fn run_ucmd_as_root( ts: &TestScenario, args: &[&str], +) -> std::result::Result { + run_ucmd_as_root_with_stdin_stdout(ts, args, None, None) +} + +#[cfg(unix)] +pub fn run_ucmd_as_root_with_stdin_stdout( + ts: &TestScenario, + args: &[&str], + stdin: Option<&str>, + stdout: Option<&str>, ) -> std::result::Result { if is_ci() { Err(format!("{UUTILS_INFO}: {}", "cannot run inside CI")) @@ -2546,16 +2556,21 @@ pub fn run_ucmd_as_root( Ok(output) if String::from_utf8_lossy(&output.stdout).eq("root\n") => { // we can run sudo and we're root // run ucmd as root: - Ok(ts - .cmd("sudo") - .env("PATH", PATH) + let mut cmd = ts.cmd("sudo"); + cmd.env("PATH", PATH) .envs(DEFAULT_ENV) .arg("-E") .arg("--non-interactive") .arg(&ts.bin_path) .arg(&ts.util_name) - .args(args) - .run()) + .args(args); + if let Some(stdin) = stdin { + cmd.set_stdin(File::open(stdin).unwrap()); + } + if let Some(stdout) = stdout { + cmd.set_stdout(File::open(stdout).unwrap()); + } + Ok(cmd.run()) } Ok(output) if String::from_utf8_lossy(&output.stderr).eq("sudo: a password is required\n") => From 8a9b29ddfb5b0486c6461b6d85cebb64c63586a5 Mon Sep 17 00:00:00 2001 From: Benjamin Bara Date: Sat, 18 Mar 2023 08:54:47 +0100 Subject: [PATCH 0119/2851] dd: fix GNU test 'dd/skip-seek-past-dev' --- src/uu/dd/src/dd.rs | 57 ++++++++++++++++++++++++++++++++++++++------- 1 file changed, 48 insertions(+), 9 deletions(-) diff --git a/src/uu/dd/src/dd.rs b/src/uu/dd/src/dd.rs index 4ac2aa78006..b79ae22da4e 100644 --- a/src/uu/dd/src/dd.rs +++ b/src/uu/dd/src/dd.rs @@ -3,7 +3,7 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// spell-checker:ignore fname, tname, fpath, specfile, testfile, unspec, ifile, ofile, outfile, fullblock, urand, fileio, atoe, atoibm, behaviour, bmax, bremain, cflags, creat, ctable, ctty, datastructures, doesnt, etoa, fileout, fname, gnudd, iconvflags, iseek, nocache, noctty, noerror, nofollow, nolinks, nonblock, oconvflags, oseek, outfile, parseargs, rlen, rmax, rremain, rsofar, rstat, sigusr, wlen, wstat seekable oconv canonicalized fadvise Fadvise FADV DONTNEED ESPIPE +// spell-checker:ignore fname, ftype, tname, fpath, specfile, testfile, unspec, ifile, ofile, outfile, fullblock, urand, fileio, atoe, atoibm, behaviour, bmax, bremain, cflags, creat, ctable, ctty, datastructures, doesnt, etoa, fileout, fname, gnudd, iconvflags, iseek, nocache, noctty, noerror, nofollow, nolinks, nonblock, oconvflags, oseek, outfile, parseargs, rlen, rmax, rremain, rsofar, rstat, sigusr, wlen, wstat seekable oconv canonicalized fadvise Fadvise FADV DONTNEED ESPIPE mod datastructures; use datastructures::*; @@ -49,6 +49,8 @@ use nix::{ fcntl::{posix_fadvise, PosixFadviseAdvice}, }; use uucore::display::Quotable; +#[cfg(unix)] +use uucore::error::set_exit_code; use uucore::error::{FromIo, UResult}; use uucore::{format_usage, help_about, help_section, help_usage, show_error}; #[cfg(target_os = "linux")] @@ -200,14 +202,25 @@ impl Source { Err(e) => Err(e), }, #[cfg(unix)] - Self::StdinFile(f) => match io::copy(&mut f.take(n), &mut io::sink()) { - Ok(m) if m < n => { - show_error!("'standard input': cannot skip to specified offset"); - Ok(m) + Self::StdinFile(f) => { + if let Ok(Some(len)) = try_get_len_of_block_device(f) { + if len < n { + // GNU compatibility: + // this case prints the stats but sets the exit code to 1 + show_error!("'standard input': cannot skip: Invalid argument"); + set_exit_code(1); + return Ok(len); + } } - Ok(m) => Ok(m), - Err(e) => Err(e), - }, + match io::copy(&mut f.take(n), &mut io::sink()) { + Ok(m) if m < n => { + show_error!("'standard input': cannot skip to specified offset"); + Ok(m) + } + Ok(m) => Ok(m), + Err(e) => Err(e), + } + } Self::File(f) => f.seek(io::SeekFrom::Start(n)), #[cfg(unix)] Self::Fifo(f) => io::copy(&mut f.take(n), &mut io::sink()), @@ -527,7 +540,19 @@ impl Dest { fn seek(&mut self, n: u64) -> io::Result { match self { Self::Stdout(stdout) => io::copy(&mut io::repeat(0).take(n), stdout), - Self::File(f, _) => f.seek(io::SeekFrom::Start(n)), + Self::File(f, _) => { + #[cfg(unix)] + if let Ok(Some(len)) = try_get_len_of_block_device(f) { + if len < n { + // GNU compatibility: + // this case prints the stats but sets the exit code to 1 + show_error!("'standard output': cannot seek: Invalid argument"); + set_exit_code(1); + return Ok(len); + } + } + f.seek(io::SeekFrom::Start(n)) + } #[cfg(unix)] Self::Fifo(f) => { // Seeking in a named pipe means *reading* from the pipe. @@ -1133,6 +1158,20 @@ fn is_stdout_redirected_to_seekable_file() -> bool { } } +/// Try to get the len if it is a block device +#[cfg(unix)] +fn try_get_len_of_block_device(file: &mut File) -> io::Result> { + let ftype = file.metadata()?.file_type(); + if !ftype.is_block_device() { + return Ok(None); + } + + // FIXME: this can be replaced by file.stream_len() when stable. + let len = file.seek(SeekFrom::End(0))?; + file.rewind()?; + Ok(Some(len)) +} + /// Decide whether the named file is a named pipe, also known as a FIFO. #[cfg(unix)] fn is_fifo(filename: &str) -> bool { From 17f4d17021148b038e2f2c7942eea6004e06a06c Mon Sep 17 00:00:00 2001 From: Benjamin Bara Date: Sat, 22 Jul 2023 11:26:56 +0200 Subject: [PATCH 0120/2851] tests: dd: add skip-seek-past-dev tests These tests try to read or write past a block device, where the block device is either given as stdin or stdout. It requires access to the block device, and therefore is executed as root. For now, it is assumed that a block device "/dev/sda1" with a size smaller than 10000000000000000 exists. --- tests/by-util/test_dd.rs | 44 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/tests/by-util/test_dd.rs b/tests/by-util/test_dd.rs index fe38acca47b..8ebf57c1c84 100644 --- a/tests/by-util/test_dd.rs +++ b/tests/by-util/test_dd.rs @@ -4,6 +4,8 @@ // file that was distributed with this source code. // spell-checker:ignore fname, tname, fpath, specfile, testfile, unspec, ifile, ofile, outfile, fullblock, urand, fileio, atoe, atoibm, availible, behaviour, bmax, bremain, btotal, cflags, creat, ctable, ctty, datastructures, doesnt, etoa, fileout, fname, gnudd, iconvflags, iseek, nocache, noctty, noerror, nofollow, nolinks, nonblock, oconvflags, oseek, outfile, parseargs, rlen, rmax, rposition, rremain, rsofar, rstat, sigusr, sigval, wlen, wstat abcdefghijklm abcdefghi nabcde nabcdefg abcdefg +#[cfg(unix)] +use crate::common::util::run_ucmd_as_root_with_stdin_stdout; use crate::common::util::TestScenario; #[cfg(all(not(windows), feature = "printf"))] use crate::common::util::{UCommand, TESTS_BINARY}; @@ -1566,3 +1568,45 @@ fn test_nocache_file() { .succeeds() .stderr_only("2048+0 records in\n2048+0 records out\n"); } + +#[test] +#[cfg(unix)] +fn test_skip_past_dev() { + // NOTE: This test intends to trigger code which can only be reached with root permissions. + let ts = TestScenario::new(util_name!()); + + if let Ok(result) = run_ucmd_as_root_with_stdin_stdout( + &ts, + &["bs=1", "skip=10000000000000000", "count=0", "status=noxfer"], + Some("/dev/sda1"), + None, + ) { + result.stderr_contains("dd: 'standard input': cannot skip: Invalid argument"); + result.stderr_contains("0+0 records in"); + result.stderr_contains("0+0 records out"); + result.code_is(1); + } else { + print!("TEST SKIPPED"); + } +} + +#[test] +#[cfg(unix)] +fn test_seek_past_dev() { + // NOTE: This test intends to trigger code which can only be reached with root permissions. + let ts = TestScenario::new(util_name!()); + + if let Ok(result) = run_ucmd_as_root_with_stdin_stdout( + &ts, + &["bs=1", "seek=10000000000000000", "count=0", "status=noxfer"], + None, + Some("/dev/sda1"), + ) { + result.stderr_contains("dd: 'standard output': cannot seek: Invalid argument"); + result.stderr_contains("0+0 records in"); + result.stderr_contains("0+0 records out"); + result.code_is(1); + } else { + print!("TEST SKIPPED"); + } +} From bd0fb817a7ae32f0dc6fafe6aa2ade7e81d3d05b Mon Sep 17 00:00:00 2001 From: tommady Date: Sun, 24 Sep 2023 16:53:27 +0800 Subject: [PATCH 0121/2851] cp: fix the result of inodes are not the same when preserve links is flagged (#5064) Should fix: ``` rm -rf a b c touch a ln -s a b mkdir c ./target/debug/coreutils cp --preserve=links -R -H a b c a_inode=$(ls -i c/a|sed 's,c/.*,,') b_inode=$(ls -i c/b|sed 's,c/.*,,') echo "$a_inode" = "$b_inode" ``` --- src/uu/cp/src/copydir.rs | 57 ++++++----- src/uu/cp/src/cp.rs | 158 ++++++++++++++----------------- tests/by-util/test_cp.rs | 199 ++++++++++++++++++++++++++++++++++++++- 3 files changed, 296 insertions(+), 118 deletions(-) diff --git a/src/uu/cp/src/copydir.rs b/src/uu/cp/src/copydir.rs index 430548d0cf3..ac44ce68752 100644 --- a/src/uu/cp/src/copydir.rs +++ b/src/uu/cp/src/copydir.rs @@ -8,7 +8,7 @@ //! See the [`copy_directory`] function for more information. #[cfg(windows)] use std::borrow::Cow; -use std::collections::HashSet; +use std::collections::{HashMap, HashSet}; use std::env; use std::fs; use std::io; @@ -24,8 +24,8 @@ use uucore::uio_error; use walkdir::{DirEntry, WalkDir}; use crate::{ - aligned_ancestors, context_for, copy_attributes, copy_file, copy_link, preserve_hardlinks, - CopyResult, Error, Options, + aligned_ancestors, context_for, copy_attributes, copy_file, copy_link, CopyResult, Error, + Options, }; /// Ensure a Windows path starts with a `\\?`. @@ -200,7 +200,7 @@ fn copy_direntry( options: &Options, symlinked_files: &mut HashSet, preserve_hard_links: bool, - hard_links: &mut Vec<(String, u64)>, + copied_files: &mut HashMap, ) -> CopyResult<()> { let Entry { source_absolute, @@ -240,30 +240,27 @@ fn copy_direntry( // If the source is not a directory, then we need to copy the file. if !source_absolute.is_dir() { if preserve_hard_links { - let dest = local_to_target.as_path().to_path_buf(); - let found_hard_link = preserve_hardlinks(hard_links, &source_absolute, &dest)?; - if !found_hard_link { - match copy_file( - progress_bar, - &source_absolute, - local_to_target.as_path(), - options, - symlinked_files, - false, - ) { - Ok(_) => Ok(()), - Err(err) => { - if source_absolute.is_symlink() { - // silent the error with a symlink - // In case we do --archive, we might copy the symlink - // before the file itself - Ok(()) - } else { - Err(err) - } + match copy_file( + progress_bar, + &source_absolute, + local_to_target.as_path(), + options, + symlinked_files, + copied_files, + false, + ) { + Ok(_) => Ok(()), + Err(err) => { + if source_absolute.is_symlink() { + // silent the error with a symlink + // In case we do --archive, we might copy the symlink + // before the file itself + Ok(()) + } else { + Err(err) } - }?; - } + } + }?; } else { // At this point, `path` is just a plain old file. // Terminate this function immediately if there is any @@ -277,6 +274,7 @@ fn copy_direntry( local_to_target.as_path(), options, symlinked_files, + copied_files, false, ) { Ok(_) => {} @@ -310,6 +308,7 @@ pub(crate) fn copy_directory( target: &Path, options: &Options, symlinked_files: &mut HashSet, + copied_files: &mut HashMap, source_in_command_line: bool, ) -> CopyResult<()> { if !options.recursive { @@ -324,6 +323,7 @@ pub(crate) fn copy_directory( target, options, symlinked_files, + copied_files, source_in_command_line, ); } @@ -372,7 +372,6 @@ pub(crate) fn copy_directory( }; let target = tmp.as_path(); - let mut hard_links: Vec<(String, u64)> = vec![]; let preserve_hard_links = options.preserve_hard_links(); // Collect some paths here that are invariant during the traversal @@ -397,7 +396,7 @@ pub(crate) fn copy_directory( options, symlinked_files, preserve_hard_links, - &mut hard_links, + copied_files, )?; } // Print an error message, but continue traversing the directory. diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index 516f20fdad5..05261c22fe2 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -9,7 +9,7 @@ use quick_error::quick_error; use std::borrow::Cow; use std::cmp::Ordering; -use std::collections::HashSet; +use std::collections::{HashMap, HashSet}; use std::env; #[cfg(not(windows))] use std::ffi::CString; @@ -1106,67 +1106,6 @@ fn parse_path_args( Ok((paths, target)) } -/// Get the inode information for a file. -fn get_inode(file_info: &FileInformation) -> u64 { - #[cfg(unix)] - let result = file_info.inode(); - #[cfg(windows)] - let result = file_info.file_index(); - result -} - -#[cfg(target_os = "redox")] -fn preserve_hardlinks( - hard_links: &mut Vec<(String, u64)>, - source: &std::path::Path, - dest: &std::path::Path, - found_hard_link: &mut bool, -) -> CopyResult<()> { - // Redox does not currently support hard links - Ok(()) -} - -/// Hard link a pair of files if needed _and_ record if this pair is a new hard link. -#[cfg(not(target_os = "redox"))] -fn preserve_hardlinks( - hard_links: &mut Vec<(String, u64)>, - source: &std::path::Path, - dest: &std::path::Path, -) -> CopyResult { - let info = FileInformation::from_path(source, false) - .context(format!("cannot stat {}", source.quote()))?; - let inode = get_inode(&info); - let nlinks = info.number_of_links(); - let mut found_hard_link = false; - #[allow(clippy::explicit_iter_loop)] - for (link, link_inode) in hard_links.iter() { - if *link_inode == inode { - // Consider the following files: - // - // * `src/f` - a regular file - // * `src/link` - a hard link to `src/f` - // * `dest/src/f` - a different regular file - // - // In this scenario, if we do `cp -a src/ dest/`, it is - // possible that the order of traversal causes `src/link` - // to get copied first (to `dest/src/link`). In that case, - // in order to make sure `dest/src/link` is a hard link to - // `dest/src/f` and `dest/src/f` has the contents of - // `src/f`, we delete the existing file to allow the hard - // linking. - if file_or_link_exists(dest) && file_or_link_exists(Path::new(link)) { - std::fs::remove_file(dest)?; - } - std::fs::hard_link(link, dest).unwrap(); - found_hard_link = true; - } - } - if !found_hard_link && nlinks > 1 { - hard_links.push((dest.to_str().unwrap().to_string(), inode)); - } - Ok(found_hard_link) -} - /// When handling errors, we don't always want to show them to the user. This function handles that. fn show_error_if_needed(error: &Error) { match error { @@ -1195,14 +1134,19 @@ pub fn copy(sources: &[PathBuf], target: &Path, options: &Options) -> CopyResult let target_type = TargetType::determine(sources, target); verify_target_type(target, &target_type)?; - let preserve_hard_links = options.preserve_hard_links(); - - let mut hard_links: Vec<(String, u64)> = vec![]; - let mut non_fatal_errors = false; let mut seen_sources = HashSet::with_capacity(sources.len()); let mut symlinked_files = HashSet::new(); + // to remember the copied files for further usage. + // the FileInformation implemented the Hash trait by using + // 1. inode number + // 2. device number + // the combination of a file's inode number and device number is unique throughout all the file systems. + // + // key is the source file's information and the value is the destination filepath. + let mut copied_files: HashMap = HashMap::with_capacity(sources.len()); + let progress_bar = if options.progress_bar { let pb = ProgressBar::new(disk_usage(sources, options.recursive)?) .with_style( @@ -1222,28 +1166,19 @@ pub fn copy(sources: &[PathBuf], target: &Path, options: &Options) -> CopyResult if seen_sources.contains(source) { // FIXME: compare sources by the actual file they point to, not their path. (e.g. dir/file == dir/../dir/file in most cases) show_warning!("source {} specified more than once", source.quote()); - } else { - let found_hard_link = if preserve_hard_links && !source.is_dir() { - let dest = construct_dest_path(source, target, &target_type, options)?; - preserve_hardlinks(&mut hard_links, source, &dest)? - } else { - false - }; - if !found_hard_link { - if let Err(error) = copy_source( - &progress_bar, - source, - target, - &target_type, - options, - &mut symlinked_files, - ) { - show_error_if_needed(&error); - non_fatal_errors = true; - } - } - seen_sources.insert(source); + } else if let Err(error) = copy_source( + &progress_bar, + source, + target, + &target_type, + options, + &mut symlinked_files, + &mut copied_files, + ) { + show_error_if_needed(&error); + non_fatal_errors = true; } + seen_sources.insert(source); } if let Some(pb) = progress_bar { @@ -1295,11 +1230,20 @@ fn copy_source( target_type: &TargetType, options: &Options, symlinked_files: &mut HashSet, + copied_files: &mut HashMap, ) -> CopyResult<()> { let source_path = Path::new(&source); if source_path.is_dir() { // Copy as directory - copy_directory(progress_bar, source, target, options, symlinked_files, true) + copy_directory( + progress_bar, + source, + target, + options, + symlinked_files, + copied_files, + true, + ) } else { // Copy as file let dest = construct_dest_path(source_path, target, target_type, options)?; @@ -1309,6 +1253,7 @@ fn copy_source( dest.as_path(), options, symlinked_files, + copied_files, true, ); if options.parents { @@ -1570,6 +1515,24 @@ fn handle_existing_dest( OverwriteMode::Clobber(ClobberMode::RemoveDestination) => { fs::remove_file(dest)?; } + OverwriteMode::Clobber(ClobberMode::Standard) => { + // Consider the following files: + // + // * `src/f` - a regular file + // * `src/link` - a hard link to `src/f` + // * `dest/src/f` - a different regular file + // + // In this scenario, if we do `cp -a src/ dest/`, it is + // possible that the order of traversal causes `src/link` + // to get copied first (to `dest/src/link`). In that case, + // in order to make sure `dest/src/link` is a hard link to + // `dest/src/f` and `dest/src/f` has the contents of + // `src/f`, we delete the existing file to allow the hard + // linking. + if options.preserve_hard_links() { + fs::remove_file(dest)?; + } + } _ => (), }; @@ -1643,6 +1606,7 @@ fn copy_file( dest: &Path, options: &Options, symlinked_files: &mut HashSet, + copied_files: &mut HashMap, source_in_command_line: bool, ) -> CopyResult<()> { if (options.update == UpdateMode::ReplaceIfOlder || options.update == UpdateMode::ReplaceNone) @@ -1686,6 +1650,19 @@ fn copy_file( handle_existing_dest(source, dest, options, source_in_command_line)?; } + if options.preserve_hard_links() { + // if we encounter a matching device/inode pair in the source tree + // we can arrange to create a hard link between the corresponding names + // in the destination tree. + if let Some(new_source) = copied_files.get( + &FileInformation::from_path(source, options.dereference(source_in_command_line)) + .context(format!("cannot stat {}", source.quote()))?, + ) { + std::fs::hard_link(new_source, dest)?; + return Ok(()); + }; + } + if options.verbose { if let Some(pb) = progress_bar { // Suspend (hide) the progress bar so the println won't overlap with the progress bar. @@ -1873,6 +1850,11 @@ fn copy_file( copy_attributes(source, dest, &options.attributes)?; + copied_files.insert( + FileInformation::from_path(source, options.dereference(source_in_command_line))?, + dest.to_path_buf(), + ); + if let Some(progress_bar) = progress_bar { progress_bar.inc(fs::metadata(source)?.len()); } diff --git a/tests/by-util/test_cp.rs b/tests/by-util/test_cp.rs index c77be3d4ed9..afac4cd3a6c 100644 --- a/tests/by-util/test_cp.rs +++ b/tests/by-util/test_cp.rs @@ -11,7 +11,7 @@ use std::fs::set_permissions; #[cfg(not(windows))] use std::os::unix::fs; -#[cfg(all(unix, not(target_os = "freebsd")))] +#[cfg(unix)] use std::os::unix::fs::MetadataExt; #[cfg(all(unix, not(target_os = "freebsd")))] use std::os::unix::fs::PermissionsExt; @@ -1353,6 +1353,203 @@ fn test_cp_preserve_xattr_fails_on_android() { .fails(); } +#[test] +// android platform will causing stderr = cp: Permission denied (os error 13) +#[cfg(not(target_os = "android"))] +fn test_cp_preserve_links_case_1() { + let (at, mut ucmd) = at_and_ucmd!(); + + at.touch("a"); + at.hard_link("a", "b"); + at.mkdir("c"); + + ucmd.arg("-d").arg("a").arg("b").arg("c").succeeds(); + + assert!(at.dir_exists("c")); + assert!(at.plus("c").join("a").exists()); + assert!(at.plus("c").join("b").exists()); + + #[cfg(unix)] + { + let metadata_a = std::fs::metadata(at.subdir.join("c").join("a")).unwrap(); + let metadata_b = std::fs::metadata(at.subdir.join("c").join("b")).unwrap(); + + assert_eq!(metadata_a.ino(), metadata_b.ino()); + } +} + +#[test] +// android platform will causing stderr = cp: Permission denied (os error 13) +#[cfg(not(target_os = "android"))] +fn test_cp_preserve_links_case_2() { + let (at, mut ucmd) = at_and_ucmd!(); + + at.touch("a"); + at.symlink_file("a", "b"); + at.mkdir("c"); + + ucmd.arg("--preserve=links") + .arg("-R") + .arg("-H") + .arg("a") + .arg("b") + .arg("c") + .succeeds(); + + assert!(at.dir_exists("c")); + assert!(at.plus("c").join("a").exists()); + assert!(at.plus("c").join("b").exists()); + + #[cfg(unix)] + { + let metadata_a = std::fs::metadata(at.subdir.join("c").join("a")).unwrap(); + let metadata_b = std::fs::metadata(at.subdir.join("c").join("b")).unwrap(); + + assert_eq!(metadata_a.ino(), metadata_b.ino()); + } +} + +#[test] +// android platform will causing stderr = cp: Permission denied (os error 13) +#[cfg(not(target_os = "android"))] +fn test_cp_preserve_links_case_3() { + let (at, mut ucmd) = at_and_ucmd!(); + + at.mkdir("d"); + at.touch("d/a"); + at.symlink_file("d/a", "d/b"); + + ucmd.arg("--preserve=links") + .arg("-R") + .arg("-L") + .arg("d") + .arg("c") + .succeeds(); + + assert!(at.dir_exists("c")); + assert!(at.plus("c").join("a").exists()); + assert!(at.plus("c").join("b").exists()); + + #[cfg(unix)] + { + let metadata_a = std::fs::metadata(at.subdir.join("c").join("a")).unwrap(); + let metadata_b = std::fs::metadata(at.subdir.join("c").join("b")).unwrap(); + + assert_eq!(metadata_a.ino(), metadata_b.ino()); + } +} + +#[test] +// android platform will causing stderr = cp: Permission denied (os error 13) +#[cfg(not(target_os = "android"))] +fn test_cp_preserve_links_case_4() { + let (at, mut ucmd) = at_and_ucmd!(); + + at.mkdir("d"); + at.touch("d/a"); + at.hard_link("d/a", "d/b"); + + ucmd.arg("--preserve=links") + .arg("-R") + .arg("-L") + .arg("d") + .arg("c") + .succeeds(); + + assert!(at.dir_exists("c")); + assert!(at.plus("c").join("a").exists()); + assert!(at.plus("c").join("b").exists()); + + #[cfg(unix)] + { + let metadata_a = std::fs::metadata(at.subdir.join("c").join("a")).unwrap(); + let metadata_b = std::fs::metadata(at.subdir.join("c").join("b")).unwrap(); + + assert_eq!(metadata_a.ino(), metadata_b.ino()); + } +} + +#[test] +// android platform will causing stderr = cp: Permission denied (os error 13) +#[cfg(not(target_os = "android"))] +fn test_cp_preserve_links_case_5() { + let (at, mut ucmd) = at_and_ucmd!(); + + at.mkdir("d"); + at.touch("d/a"); + at.hard_link("d/a", "d/b"); + + ucmd.arg("-dR") + .arg("--no-preserve=links") + .arg("d") + .arg("c") + .succeeds(); + + assert!(at.dir_exists("c")); + assert!(at.plus("c").join("a").exists()); + assert!(at.plus("c").join("b").exists()); + + #[cfg(unix)] + { + let metadata_a = std::fs::metadata(at.subdir.join("c").join("a")).unwrap(); + let metadata_b = std::fs::metadata(at.subdir.join("c").join("b")).unwrap(); + + assert_eq!(metadata_a.ino(), metadata_b.ino()); + } +} + +#[test] +// android platform will causing stderr = cp: Permission denied (os error 13) +#[cfg(not(target_os = "android"))] +fn test_cp_preserve_links_case_6() { + let (at, mut ucmd) = at_and_ucmd!(); + + at.touch("a"); + at.hard_link("a", "b"); + at.mkdir("c"); + + ucmd.arg("-d").arg("a").arg("b").arg("c").succeeds(); + + assert!(at.dir_exists("c")); + assert!(at.plus("c").join("a").exists()); + assert!(at.plus("c").join("b").exists()); + + #[cfg(unix)] + { + let metadata_a = std::fs::metadata(at.subdir.join("c").join("a")).unwrap(); + let metadata_b = std::fs::metadata(at.subdir.join("c").join("b")).unwrap(); + + assert_eq!(metadata_a.ino(), metadata_b.ino()); + } +} + +#[test] +// android platform will causing stderr = cp: Permission denied (os error 13) +#[cfg(not(target_os = "android"))] +fn test_cp_preserve_links_case_7() { + let (at, mut ucmd) = at_and_ucmd!(); + + at.mkdir("src"); + at.touch("src/f"); + at.hard_link("src/f", "src/g"); + + at.mkdir("dest"); + at.touch("dest/g"); + + ucmd.arg("-n") + .arg("--preserve=links") + .arg("src/f") + .arg("src/g") + .arg("dest") + .fails() + .stderr_contains("not replacing"); + (); + + assert!(at.dir_exists("dest")); + assert!(at.plus("dest").join("f").exists()); + assert!(at.plus("dest").join("g").exists()); +} + #[test] // For now, disable the test on Windows. Symlinks aren't well support on Windows. // It works on Unix for now and it works locally when run from a powershell From 1dcf3e63535d876b2c9e7e9680cf3fae28b5e8a0 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Sun, 24 Sep 2023 14:13:16 +0200 Subject: [PATCH 0122/2851] expose whoami function (for nushell!) --- src/uu/whoami/src/whoami.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/uu/whoami/src/whoami.rs b/src/uu/whoami/src/whoami.rs index cb8bcd9c735..738f7509a4d 100644 --- a/src/uu/whoami/src/whoami.rs +++ b/src/uu/whoami/src/whoami.rs @@ -5,6 +5,8 @@ /* last synced with: whoami (GNU coreutils) 8.21 */ +use std::ffi::OsString; + use clap::{crate_version, Command}; use uucore::display::println_verbatim; @@ -19,11 +21,16 @@ const USAGE: &str = help_usage!("whoami.md"); #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { uu_app().try_get_matches_from(args)?; - let username = platform::get_username().map_err_context(|| "failed to get username".into())?; + let username = whoami()?; println_verbatim(username).map_err_context(|| "failed to print username".into())?; Ok(()) } +/// Get the current username +pub fn whoami() -> UResult { + platform::get_username().map_err_context(|| "failed to get username".into()) +} + pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(crate_version!()) From b90b59c0035f3296b3defc0fc1196660f1e3ade6 Mon Sep 17 00:00:00 2001 From: Leviticoh <67531886+Leviticoh@users.noreply.github.com> Date: Sun, 24 Sep 2023 14:44:44 +0200 Subject: [PATCH 0123/2851] uniq: added support for deprecated `-N` option (#4228) --- src/uu/uniq/src/uniq.rs | 80 +++++++++++++++++- tests/by-util/test_uniq.rs | 92 +++++++++++++++------ tests/fixtures/uniq/skip-2-fields.expected | 3 + tests/fixtures/uniq/skip-21-fields.expected | 3 + tests/fixtures/uniq/skip-fields.txt | 3 + 5 files changed, 150 insertions(+), 31 deletions(-) create mode 100644 tests/fixtures/uniq/skip-21-fields.expected diff --git a/src/uu/uniq/src/uniq.rs b/src/uu/uniq/src/uniq.rs index 3aac7b83467..72338bf9602 100644 --- a/src/uu/uniq/src/uniq.rs +++ b/src/uu/uniq/src/uniq.rs @@ -3,7 +3,7 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -use clap::{builder::ValueParser, crate_version, Arg, ArgAction, ArgMatches, Command}; +use clap::{builder::ValueParser, crate_version, Arg, ArgAction, ArgGroup, ArgMatches, Command}; use std::ffi::{OsStr, OsString}; use std::fs::File; use std::io::{self, stdin, stdout, BufRead, BufReader, BufWriter, Write}; @@ -23,6 +23,7 @@ pub mod options { pub static IGNORE_CASE: &str = "ignore-case"; pub static REPEATED: &str = "repeated"; pub static SKIP_FIELDS: &str = "skip-fields"; + pub static OBSOLETE_SKIP_FIELDS: &str = "obsolete_skip_field"; pub static SKIP_CHARS: &str = "skip-chars"; pub static UNIQUE: &str = "unique"; pub static ZERO_TERMINATED: &str = "zero-terminated"; @@ -53,6 +54,8 @@ struct Uniq { zero_terminated: bool, } +const OBSOLETE_SKIP_FIELDS_DIGITS: [&str; 10] = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]; + macro_rules! write_line_terminator { ($writer:expr, $line_terminator:expr) => { $writer @@ -236,6 +239,41 @@ fn opt_parsed(opt_name: &str, matches: &ArgMatches) -> UResult UResult> { + for opt_text in OBSOLETE_SKIP_FIELDS_DIGITS { + let argument = matches.get_one::(opt_text); + if matches.contains_id(opt_text) { + let mut full = opt_text.to_owned(); + if let Some(ar) = argument { + full.push_str(ar); + } + let value = full.parse::(); + + if let Ok(val) = value { + return Ok(Some(val)); + } else { + return Err(USimpleError { + code: 1, + message: format!("Invalid argument for skip-fields: {}", full), + } + .into()); + } + } + } + Ok(None) +} + #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { let matches = uu_app().after_help(AFTER_HELP).try_get_matches_from(args)?; @@ -247,6 +285,10 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { .map(|mut fi| (fi.next(), fi.next())) .unwrap_or_default(); + let skip_fields_modern: Option = opt_parsed(options::SKIP_FIELDS, &matches)?; + + let skip_fields_old: Option = obsolete_skip_field(&matches)?; + let uniq = Uniq { repeats_only: matches.get_flag(options::REPEATED) || matches.contains_id(options::ALL_REPEATED), @@ -255,7 +297,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { || matches.contains_id(options::GROUP), delimiters: get_delimiter(&matches), show_counts: matches.get_flag(options::COUNT), - skip_fields: opt_parsed(options::SKIP_FIELDS, &matches)?, + skip_fields: skip_fields_modern.or(skip_fields_old), slice_start: opt_parsed(options::SKIP_CHARS, &matches)?, slice_stop: opt_parsed(options::CHECK_CHARS, &matches)?, ignore_case: matches.get_flag(options::IGNORE_CASE), @@ -276,7 +318,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } pub fn uu_app() -> Command { - Command::new(uucore::util_name()) + let mut cmd = Command::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) .override_usage(format_usage(USAGE)) @@ -355,6 +397,7 @@ pub fn uu_app() -> Command { Arg::new(options::SKIP_FIELDS) .short('f') .long(options::SKIP_FIELDS) + .overrides_with_all(OBSOLETE_SKIP_FIELDS_DIGITS) .help("avoid comparing the first N fields") .value_name("N"), ) @@ -372,13 +415,42 @@ pub fn uu_app() -> Command { .help("end lines with 0 byte, not newline") .action(ArgAction::SetTrue), ) + .group( + // in GNU `uniq` every every digit of these arguments + // would be interpreted as a simple flag, + // these flags then are concatenated to get + // the number of fields to skip. + // in this way `uniq -1 -z -2` would be + // equal to `uniq -12 -q`, since this behavior + // is counterintuitive and it's hard to do in clap + // we handle it more like GNU `fold`: we have a flag + // for each possible initial digit, that takes the + // rest of the value as argument. + // we disallow explicitly multiple occurrences + // because then it would have a different behavior + // from GNU + ArgGroup::new(options::OBSOLETE_SKIP_FIELDS) + .multiple(false) + .args(OBSOLETE_SKIP_FIELDS_DIGITS) + ) .arg( Arg::new(ARG_FILES) .action(ArgAction::Append) .value_parser(ValueParser::os_string()) .num_args(0..=2) .value_hint(clap::ValueHint::FilePath), - ) + ); + + for i in OBSOLETE_SKIP_FIELDS_DIGITS { + cmd = cmd.arg( + Arg::new(i) + .short(i.chars().next().unwrap()) + .num_args(0..=1) + .hide(true), + ); + } + + cmd } fn get_delimiter(matches: &ArgMatches) -> Delimiters { diff --git a/tests/by-util/test_uniq.rs b/tests/by-util/test_uniq.rs index 97a829b9f7a..aa41de8274d 100644 --- a/tests/by-util/test_uniq.rs +++ b/tests/by-util/test_uniq.rs @@ -80,7 +80,7 @@ fn test_stdin_skip_and_check_2_chars() { } #[test] -fn test_stdin_skip_1_field() { +fn test_stdin_skip_2_fields() { new_ucmd!() .args(&["-f2"]) .pipe_in_fixture(SKIP_FIELDS) @@ -88,6 +88,42 @@ fn test_stdin_skip_1_field() { .stdout_is_fixture("skip-2-fields.expected"); } +#[test] +fn test_stdin_skip_2_fields_obsolete() { + new_ucmd!() + .args(&["-2"]) + .pipe_in_fixture(SKIP_FIELDS) + .run() + .stdout_is_fixture("skip-2-fields.expected"); +} + +#[test] +fn test_stdin_skip_21_fields() { + new_ucmd!() + .args(&["-f21"]) + .pipe_in_fixture(SKIP_FIELDS) + .run() + .stdout_is_fixture("skip-21-fields.expected"); +} + +#[test] +fn test_stdin_skip_21_fields_obsolete() { + new_ucmd!() + .args(&["-21"]) + .pipe_in_fixture(SKIP_FIELDS) + .run() + .stdout_is_fixture("skip-21-fields.expected"); +} + +#[test] +fn test_stdin_skip_invalid_fields_obsolete() { + new_ucmd!() + .args(&["-5deadbeef"]) + .run() + .failure() + .stderr_only("uniq: Invalid argument for skip-fields: 5deadbeef\n"); +} + #[test] fn test_stdin_all_repeated() { new_ucmd!() @@ -436,15 +472,15 @@ fn gnu_tests() { stderr: None, exit: None, }, - // // Obsolete syntax for "-f 1" - // TestCase { - // name: "obs30", - // args: &["-1"], - // input: "a a\nb a\n", - // stdout: Some("a a\n"), - // stderr: None, - // exit: None, - // }, + // Obsolete syntax for "-f 1" + TestCase { + name: "obs30", + args: &["-1"], + input: "a a\nb a\n", + stdout: Some("a a\n"), + stderr: None, + exit: None, + }, TestCase { name: "31", args: &["-f", "1"], @@ -518,23 +554,25 @@ fn gnu_tests() { stderr: None, exit: None, }, - // // Obsolete syntax for "-s 1" - // TestCase { - // name: "obs-plus44", - // args: &["+1", "--"], - // input: "aaa\naaa\n", - // stdout: Some("aaa\n"), - // stderr: None, - // exit: None, - // }, - // TestCase { - // name: "obs-plus45", - // args: &["+1", "--"], - // input: "baa\naaa\n", - // stdout: Some("baa\n"), - // stderr: None, - // exit: None, - // }, + /* + // Obsolete syntax for "-s 1" + TestCase { + name: "obs-plus44", + args: &["+1", "--"], + input: "aaa\naaa\n", + stdout: Some("aaa\n"), + stderr: None, + exit: None, + }, + TestCase { + name: "obs-plus45", + args: &["+1", "--"], + input: "baa\naaa\n", + stdout: Some("baa\n"), + stderr: None, + exit: None, + }, + */ TestCase { name: "50", args: &["-f", "1", "-s", "1"], diff --git a/tests/fixtures/uniq/skip-2-fields.expected b/tests/fixtures/uniq/skip-2-fields.expected index b971c2b2b39..c7f9bde9d28 100644 --- a/tests/fixtures/uniq/skip-2-fields.expected +++ b/tests/fixtures/uniq/skip-2-fields.expected @@ -1,2 +1,5 @@ aaa ⟪⟫ a aa a +a a a a a a a a a a a a a a a a a a a b a a a +a a a a a a a a a a a a a a a a a a a a b a a +a a a a a a a a a a a a a a a a a a a a a b a diff --git a/tests/fixtures/uniq/skip-21-fields.expected b/tests/fixtures/uniq/skip-21-fields.expected new file mode 100644 index 00000000000..1f5295afa5f --- /dev/null +++ b/tests/fixtures/uniq/skip-21-fields.expected @@ -0,0 +1,3 @@ + aaa ⟪⟫ a +a a a a a a a a a a a a a a a a a a a b a a a +a a a a a a a a a a a a a a a a a a a a a b a diff --git a/tests/fixtures/uniq/skip-fields.txt b/tests/fixtures/uniq/skip-fields.txt index 4ec2744c61d..0ca708a74ca 100644 --- a/tests/fixtures/uniq/skip-fields.txt +++ b/tests/fixtures/uniq/skip-fields.txt @@ -6,3 +6,6 @@ ZZZ aa a aa a a +a a a a a a a a a a a a a a a a a a a b a a a +a a a a a a a a a a a a a a a a a a a a b a a +a a a a a a a a a a a a a a a a a a a a a b a From df6193f84c62666d9ba79ec9bf3bb3adb3f1bc36 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Sun, 24 Sep 2023 15:40:35 +0200 Subject: [PATCH 0124/2851] clean up deps.nu --- util/deps.nu | 81 ++++++++++++++++++---------------------------------- 1 file changed, 28 insertions(+), 53 deletions(-) diff --git a/util/deps.nu b/util/deps.nu index a35e2d98c96..368048fff45 100644 --- a/util/deps.nu +++ b/util/deps.nu @@ -20,11 +20,11 @@ # - `normal_dep`: whether the crate is a normal dependency. # - `build_dep`: whether the crate is a build dependency. # - `dev_dep`: whether the crate is a dev dependency. -# - `organisation`: the GitHub/GitLab organisation or user of the repository of the crate. +# - `organization`: the GitHub/GitLab organization or user of the repository of the crate. # - `repository_name`: the name of the repository the crate is in. The format is "{owner}/{repo}". # - `dependencies`: direct dependencies of the crate (in the format of Cargo.lock). # -# To use this script, start nushell (tested only on version 0.82.0), import the library and +# To use this script, start Nushell (tested only on version 0.82.0), import the library and # call `all_dep_info`: # # ``` @@ -33,19 +33,19 @@ # > let dep = (deps all_dep_info) # ``` # -# Then you can perform analysis. For example, to group the dependencies by organisation: +# Then you can perform analysis. For example, to group the dependencies by organization: # # ``` -# > $dep | group-by organisation +# > $dep | group-by organization # ``` # # Or to find all crates with multiple versions (like cargo deny): # ``` -# > $dep | where num_versions > 1 +# > $dep | where num_versions > 1 # ``` # # Ideas to expand this: -# +# # - Figure out the whole dependency graph # - Figure out which platforms and which features enable which crates # - Figure out which utils require which crates @@ -58,61 +58,37 @@ # - Check the number of owners/contributors # - Make a webpage to more easily explore the data -# Read the packages a Cargo.lock file -def read_lockfile [name: path] { - open $name | from toml | get package -} - # Read the names output by cargo tree -export def read_tree_names [edges: string, features: string] { - cargo tree -e $edges --features $features - | rg "[a-zA-Z0-9_-]+ v[0-9.]+" -o - | lines - | each {|x| parse_name_and_version $x } -} - -def parse_name_and_version [s: string] { - let s = ($s | split row " ") - - let name = $s.0 - let version = if ($s | length) > 1 { - $s.1 | str substring 1.. - } else { - "" - } - - {name: $name, version: $version} +export def read_tree_names [edge_kind: string, features: list]: any -> table<> { + cargo tree --edges $edge_kind --features ($features | str join ",") + | parse -r "(?P[a-zA-Z0-9_-]+) v(?P[0-9.]+)" } # Read the crates.io info for a list of crates names -def read_crates_io [names: list] { - let total = ($names | length) - $names | enumerate | par-each {|el| - let key = $el.index - let name = $el.item - print $"($key)/($total): ($name)" - http get $"https://crates.io/api/v1/crates/($name)" | get crate +def read_crates_io [names: list] -> any -> table<> { + let total = $names | length + $names | enumerate | par-each {|name| + print $"($name.index)/($total): ($name.item)" + http get $"https://crates.io/api/v1/crates/($name.item)" | get crate } } -def in_table [col_name, table] { - insert $col_name {|el| - $table - | any {|table_el| - $table_el.name == $el.name and $table_el.version == $el.version } - } -} - # Add column for a dependency type -def add_dep_type [dep_type: string, features: string] { - in_table $"($dep_type)_dep" (read_tree_names $dep_type $features) +def add_dep_type [dep_type: string, features: list]: table<> -> table<> { + let input_table = $in + let table = read_tree_names $dep_type $features + $input_table | insert $"($dep_type)_dep" {|outer| + $table | any {|inner| + $inner.name == $outer.name and $inner.version == $outer.version + } + } } export def all_dep_info [] { - let features = unix,feat_selinux - - let lock = (read_lockfile Cargo.lock) + let features = [unix, feat_selinux] + let lock = open Cargo.lock | from toml | get package + $lock # Add number of versions | join ($lock | group-by name | transpose | update column1 { length } | rename name num_versions) name @@ -124,10 +100,10 @@ export def all_dep_info [] { # Add crates.io info | join (read_crates_io ($lock.name | uniq)) name # Add GH org or user info - # The organisation is an indicator that crates should be treated as one dependency. - # However, there are also unrelated projects by a single organisation, so it's not + # The organization is an indicator that crates should be treated as one dependency. + # However, there are also unrelated projects by a single organization, so it's not # clear. - | insert organisation {|x| + | insert organization {|x| let repository = $x.repository? if ($repository == null) { "" } else { $repository | url parse | get path | path split | get 1 @@ -152,4 +128,3 @@ export def all_dep_info [] { } } } - From ec7ced2518386be229d154fdf42675b8255a5f5d Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Mon, 20 Mar 2023 20:55:45 +0100 Subject: [PATCH 0125/2851] Fuzz the expr command --- .github/workflows/CICD.yml | 7 ++ fuzz/Cargo.toml | 7 ++ fuzz/fuzz_targets/fuzz_common.rs | 30 +++++ fuzz/fuzz_targets/fuzz_expr.rs | 184 +++++++++++++++++++++++++++++++ 4 files changed, 228 insertions(+) create mode 100644 fuzz/fuzz_targets/fuzz_common.rs create mode 100644 fuzz/fuzz_targets/fuzz_expr.rs diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index 577e235f9cf..a9f04e3cf1d 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -149,6 +149,13 @@ jobs: ## Run it cd fuzz cargo +nightly fuzz run fuzz_test -- -max_total_time=${{ env.RUN_FOR }} -detect_leaks=0 + - name: Run fuzz_expr for XX seconds + continue-on-error: true + shell: bash + run: | + ## Run it + cd fuzz + cargo +nightly fuzz run fuzz_expr -- -max_total_time=${{ env.RUN_FOR }} -detect_leaks=0 - name: Run fuzz_parse_glob for XX seconds shell: bash run: | diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index 91a85b45a0f..549f9a6b762 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -15,6 +15,7 @@ rand = { version = "0.8", features = ["small_rng"] } uucore = { path = "../src/uucore/" } uu_date = { path = "../src/uu/date/" } uu_test = { path = "../src/uu/test/" } +uu_expr = { path = "../src/uu/expr/" } # Prevent this from interfering with workspaces @@ -27,6 +28,12 @@ path = "fuzz_targets/fuzz_date.rs" test = false doc = false +[[bin]] +name = "fuzz_expr" +path = "fuzz_targets/fuzz_expr.rs" +test = false +doc = false + [[bin]] name = "fuzz_test" path = "fuzz_targets/fuzz_test.rs" diff --git a/fuzz/fuzz_targets/fuzz_common.rs b/fuzz/fuzz_targets/fuzz_common.rs new file mode 100644 index 00000000000..fb1f498e90b --- /dev/null +++ b/fuzz/fuzz_targets/fuzz_common.rs @@ -0,0 +1,30 @@ +// This file is part of the uutils coreutils package. +// +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. + +use std::process::Command; +use std::sync::atomic::Ordering; +use std::sync::{atomic::AtomicBool, Once}; + +static CHECK_GNU: Once = Once::new(); +static IS_GNU: AtomicBool = AtomicBool::new(false); + +pub fn is_gnu_cmd(cmd_path: &str) -> Result<(), std::io::Error> { + CHECK_GNU.call_once(|| { + let version_output = Command::new(cmd_path).arg("--version").output().unwrap(); + + println!("version_output {:#?}", version_output); + + let version_str = String::from_utf8_lossy(&version_output.stdout).to_string(); + if version_str.contains("GNU coreutils") { + IS_GNU.store(true, Ordering::Relaxed); + } + }); + + if IS_GNU.load(Ordering::Relaxed) { + Ok(()) + } else { + panic!("Not the GNU implementation"); + } +} diff --git a/fuzz/fuzz_targets/fuzz_expr.rs b/fuzz/fuzz_targets/fuzz_expr.rs new file mode 100644 index 00000000000..e364342b8bc --- /dev/null +++ b/fuzz/fuzz_targets/fuzz_expr.rs @@ -0,0 +1,184 @@ +// This file is part of the uutils coreutils package. +// +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. +// spell-checker:ignore parens + +#![no_main] +use libfuzzer_sys::fuzz_target; +use uu_expr::uumain; + +use rand::seq::SliceRandom; +use rand::Rng; +use std::ffi::OsString; + +use libc::{dup, dup2, STDOUT_FILENO}; +use std::process::Command; +mod fuzz_common; +use crate::fuzz_common::is_gnu_cmd; + +static CMD_PATH: &str = "expr"; + +fn run_gnu_expr(args: &[OsString]) -> Result<(String, i32), std::io::Error> { + is_gnu_cmd(CMD_PATH)?; // Check if it's a GNU implementation + + let mut command = Command::new(CMD_PATH); + for arg in args { + command.arg(arg); + } + let output = command.output()?; + let exit_code = output.status.code().unwrap_or(-1); + if output.status.success() { + Ok(( + String::from_utf8_lossy(&output.stdout).to_string(), + exit_code, + )) + } else { + Err(std::io::Error::new( + std::io::ErrorKind::Other, + format!("GNU expr execution failed with exit code {}", exit_code), + )) + } +} + +fn generate_random_string(max_length: usize) -> String { + let mut rng = rand::thread_rng(); + let valid_utf8: Vec = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" + .chars() + .collect(); + let invalid_utf8 = [0xC3, 0x28]; // Invalid UTF-8 sequence + let mut result = String::new(); + + for _ in 0..rng.gen_range(1..=max_length) { + if rng.gen_bool(0.9) { + let ch = valid_utf8.choose(&mut rng).unwrap(); + result.push(*ch); + } else { + let ch = invalid_utf8.choose(&mut rng).unwrap(); + if let Some(c) = char::from_u32(*ch as u32) { + result.push(c); + } + } + } + + result +} + +fn generate_expr(max_depth: u32) -> String { + let mut rng = rand::thread_rng(); + let ops = ["+", "-", "*", "/", "%", "<", ">", "=", "&", "|"]; + + let mut expr = String::new(); + let mut depth = 0; + let mut last_was_operator = false; + + while depth <= max_depth { + if last_was_operator || depth == 0 { + // Add a number + expr.push_str(&rng.gen_range(1..=100).to_string()); + last_was_operator = false; + } else { + // 90% chance to add an operator followed by a number + if rng.gen_bool(0.9) { + let op = *ops.choose(&mut rng).unwrap(); + expr.push_str(&format!(" {} ", op)); + last_was_operator = true; + } + // 10% chance to add a random string (potentially invalid syntax) + else { + let random_str = generate_random_string(rng.gen_range(1..=10)); + expr.push_str(&random_str); + last_was_operator = false; + } + } + depth += 1; + } + + // Ensure the expression ends with a number if it ended with an operator + if last_was_operator { + expr.push_str(&rng.gen_range(1..=100).to_string()); + } + + expr +} + +fuzz_target!(|_data: &[u8]| { + let mut rng = rand::thread_rng(); + let expr = generate_expr(rng.gen_range(0..=20)); + let mut args = vec![OsString::from("expr")]; + args.extend(expr.split_whitespace().map(OsString::from)); + + // Save the original stdout file descriptor + let original_stdout_fd = unsafe { dup(STDOUT_FILENO) }; + + // Create a pipe to capture stdout + let mut pipe_fds = [-1; 2]; + unsafe { libc::pipe(pipe_fds.as_mut_ptr()) }; + let uumain_exit_code; + { + // Redirect stdout to the write end of the pipe + unsafe { dup2(pipe_fds[1], STDOUT_FILENO) }; + + // Run uumain with the provided arguments + uumain_exit_code = uumain(args.clone().into_iter()); + + // Restore original stdout + unsafe { dup2(original_stdout_fd, STDOUT_FILENO) }; + unsafe { libc::close(original_stdout_fd) }; + } + // Close the write end of the pipe + unsafe { libc::close(pipe_fds[1]) }; + + // Read captured output from the read end of the pipe + let mut captured_output = Vec::new(); + let mut read_buffer = [0; 1024]; + loop { + let bytes_read = unsafe { + libc::read( + pipe_fds[0], + read_buffer.as_mut_ptr() as *mut libc::c_void, + read_buffer.len(), + ) + }; + if bytes_read <= 0 { + break; + } + captured_output.extend_from_slice(&read_buffer[..bytes_read as usize]); + } + + // Close the read end of the pipe + unsafe { libc::close(pipe_fds[0]) }; + + // Convert captured output to a string + let rust_output = String::from_utf8_lossy(&captured_output) + .to_string() + .trim() + .to_owned(); + + // Run GNU expr with the provided arguments and compare the output + match run_gnu_expr(&args[1..]) { + Ok((gnu_output, gnu_exit_code)) => { + let gnu_output = gnu_output.trim().to_owned(); + if uumain_exit_code != gnu_exit_code { + println!("Expression: {}", expr); + println!("Rust code: {}", uumain_exit_code); + println!("GNU code: {}", gnu_exit_code); + panic!("Different error codes"); + } + if rust_output != gnu_output { + println!("Expression: {}", expr); + println!("Rust output: {}", rust_output); + println!("GNU output: {}", gnu_output); + panic!("Different output between Rust & GNU"); + } else { + println!( + "Outputs matched for expression: {} => Result: {}", + expr, rust_output + ); + } + } + Err(_) => { + println!("GNU expr execution failed for expression: {}", expr); + } + } +}); From 6aba3a4d67200d7ade6639d688a5a6c4510bd3f2 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 24 Sep 2023 20:04:53 +0000 Subject: [PATCH 0126/2851] fix(deps): update rust crate wild to 2.2 --- Cargo.lock | 4 ++-- src/uucore/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3723c52da62..5dc160b4dd5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3385,9 +3385,9 @@ dependencies = [ [[package]] name = "wild" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05b116685a6be0c52f5a103334cbff26db643826c7b3735fc0a3ba9871310a74" +checksum = "10d01931a94d5a115a53f95292f51d316856b68a035618eb831bbba593a30b67" dependencies = [ "glob", ] diff --git a/src/uucore/Cargo.toml b/src/uucore/Cargo.toml index 93bc618cee2..5e555da4da4 100644 --- a/src/uucore/Cargo.toml +++ b/src/uucore/Cargo.toml @@ -22,7 +22,7 @@ clap = { workspace = true } uucore_procs = { workspace = true } dns-lookup = { version = "2.0.3", optional = true } dunce = { version = "1.0.4", optional = true } -wild = "2.1" +wild = "2.2" glob = { workspace = true } # * optional itertools = { workspace = true, optional = true } From 3da58099103813c0143b3215a9e4c2f9fb83e41c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 25 Sep 2023 01:21:46 +0000 Subject: [PATCH 0127/2851] chore(deps): update rust crate memmap2 to 0.8 --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3723c52da62..c51f70851e4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1291,9 +1291,9 @@ checksum = "5486aed0026218e61b8a01d5fbd5a0a134649abb71a0e53b7bc088529dced86e" [[package]] name = "memmap2" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180d4b35be83d33392d1d1bfbd2ae1eca7ff5de1a94d3fc87faaa99a069e7cbd" +checksum = "43a5a03cefb0d953ec0be133036f14e109412fa594edc2f77227249db66cc3ed" dependencies = [ "libc", ] diff --git a/Cargo.toml b/Cargo.toml index 65b23faa2bb..15351798d9f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -292,7 +292,7 @@ lscolors = { version = "0.15.0", default-features = false, features = [ "nu-ansi-term", ] } memchr = "2" -memmap2 = "0.7" +memmap2 = "0.8" nix = { version = "0.27", default-features = false } nom = "7.1.3" notify = { version = "=6.0.1", features = ["macos_kqueue"] } From 45487d47b88d7aeb792869f48250f7c01f58507e Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Mon, 25 Sep 2023 11:43:21 +0200 Subject: [PATCH 0128/2851] extract Base enum --- src/uu/echo/src/echo.rs | 32 ++++++++++++++++++++++++-------- tests/by-util/test_echo.rs | 4 ++-- 2 files changed, 26 insertions(+), 10 deletions(-) diff --git a/src/uu/echo/src/echo.rs b/src/uu/echo/src/echo.rs index 565166842db..5ccc6a32a95 100644 --- a/src/uu/echo/src/echo.rs +++ b/src/uu/echo/src/echo.rs @@ -21,8 +21,24 @@ mod options { pub const DISABLE_BACKSLASH_ESCAPE: &str = "disable_backslash_escape"; } +#[repr(u8)] +#[derive(Clone, Copy)] +enum Base { + Oct = 8, + Hex = 16, +} + +impl Base { + fn max_digits(&self) -> u8 { + match self { + Self::Oct => 3, + Self::Hex => 2, + } + } +} + /// Parse the numeric part of the `\xHHH` and `\0NNN` escape sequences -fn parse_code(input: &mut Peekable, base: u8, max_digits: u32) -> Option { +fn parse_code(input: &mut Peekable, base: Base) -> Option { // All arithmetic on `ret` needs to be wrapping, because octal input can // take 3 digits, which is 9 bits, and therefore more than what fits in a // `u8`. GNU just seems to wrap these values. @@ -31,15 +47,15 @@ fn parse_code(input: &mut Peekable, base: u8, max_digits: u32) -> Option< // `u8::MAX` as unicode. let mut ret = input.peek().and_then(|c| c.to_digit(base as u32))? as u8; - // We can safely ifgnore the None case because we just peeked it. + // We can safely ignore the None case because we just peeked it. let _ = input.next(); - for _ in 1..max_digits { + for _ in 1..base.max_digits() { match input.peek().and_then(|c| c.to_digit(base as u32)) { - Some(n) => ret = ret.wrapping_mul(base).wrapping_add(n as u8), + Some(n) => ret = ret.wrapping_mul(base as u8).wrapping_add(n as u8), None => break, } - // We can safely ifgnore the None case because we just peeked it. + // We can safely ignore the None case because we just peeked it. let _ = input.next(); } @@ -58,7 +74,7 @@ fn print_escaped(input: &str, mut output: impl Write) -> io::Result { // Note that '0' is intentionally omitted because that // would be the \0NNN syntax. if let Some('1'..='8') = iter.peek() { - if let Some(parsed) = parse_code(&mut iter, 8, 3) { + if let Some(parsed) = parse_code(&mut iter, Base::Oct) { write!(output, "{parsed}")?; continue; } @@ -77,14 +93,14 @@ fn print_escaped(input: &str, mut output: impl Write) -> io::Result { 't' => '\t', 'v' => '\x0b', 'x' => { - if let Some(c) = parse_code(&mut iter, 16, 2) { + if let Some(c) = parse_code(&mut iter, Base::Hex) { c } else { write!(output, "\\")?; 'x' } } - '0' => parse_code(&mut iter, 8, 3).unwrap_or('\0'), + '0' => parse_code(&mut iter, Base::Oct).unwrap_or('\0'), c => { write!(output, "\\")?; c diff --git a/tests/by-util/test_echo.rs b/tests/by-util/test_echo.rs index 7de963973a2..dce5a4c9520 100644 --- a/tests/by-util/test_echo.rs +++ b/tests/by-util/test_echo.rs @@ -270,9 +270,9 @@ fn old_octal_syntax() { new_ucmd!() .arg("-e") - .arg("\\101foo") + .arg("\\101 foo") .succeeds() - .stdout_is("Afoo\n"); + .stdout_is("A foo\n"); new_ucmd!() .arg("-e") From 9fcf4cd8e5a0e3688fc2deefd36e65b927b45917 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 24 Sep 2023 22:35:27 +0200 Subject: [PATCH 0129/2851] run-gnu-test.sh: show if we can't find the file Otherwise, the error can be cryptic Co-authored-by: Daniel Hofstetter --- util/run-gnu-test.sh | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/util/run-gnu-test.sh b/util/run-gnu-test.sh index 55ba8cefc28..fde066ae196 100755 --- a/util/run-gnu-test.sh +++ b/util/run-gnu-test.sh @@ -33,7 +33,17 @@ if test $# -ge 1; then # if set, run only the tests passed SPECIFIC_TESTS="" for t in "$@"; do - SPECIFIC_TESTS="$SPECIFIC_TESTS $t" + + # Construct the full path + full_path="$path_GNU/$t" + + # Check if the file exists with .sh, .pl extension or without any extension in the $path_GNU directory + if [ -f "$full_path" ] || [ -f "$full_path.sh" ] || [ -f "$full_path.pl" ]; then + SPECIFIC_TESTS="$SPECIFIC_TESTS $t" + else + echo "Error: Test file $full_path, $full_path.sh, or $full_path.pl does not exist!" + exit 1 + fi done # trim it SPECIFIC_TESTS=$(echo $SPECIFIC_TESTS | xargs) From 22f72544a8bf96e94204929aae5475624af749dc Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Tue, 26 Sep 2023 09:33:14 +0200 Subject: [PATCH 0130/2851] run-gnu-test.sh: accept "run-root" as first param --- util/run-gnu-test.sh | 40 +++++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/util/run-gnu-test.sh b/util/run-gnu-test.sh index fde066ae196..1abb476b72b 100755 --- a/util/run-gnu-test.sh +++ b/util/run-gnu-test.sh @@ -29,25 +29,27 @@ cd "${path_GNU}" && echo "[ pwd:'${PWD}' ]" export RUST_BACKTRACE=1 -if test $# -ge 1; then - # if set, run only the tests passed - SPECIFIC_TESTS="" - for t in "$@"; do - - # Construct the full path - full_path="$path_GNU/$t" - - # Check if the file exists with .sh, .pl extension or without any extension in the $path_GNU directory - if [ -f "$full_path" ] || [ -f "$full_path.sh" ] || [ -f "$full_path.pl" ]; then - SPECIFIC_TESTS="$SPECIFIC_TESTS $t" - else - echo "Error: Test file $full_path, $full_path.sh, or $full_path.pl does not exist!" - exit 1 - fi - done - # trim it - SPECIFIC_TESTS=$(echo $SPECIFIC_TESTS | xargs) - echo "Running specific tests: $SPECIFIC_TESTS" +if test "$1" != "run-root"; then + if test $# -ge 1; then + # if set, run only the tests passed + SPECIFIC_TESTS="" + for t in "$@"; do + + # Construct the full path + full_path="$path_GNU/$t" + + # Check if the file exists with .sh, .pl extension or without any extension in the $path_GNU directory + if [ -f "$full_path" ] || [ -f "$full_path.sh" ] || [ -f "$full_path.pl" ]; then + SPECIFIC_TESTS="$SPECIFIC_TESTS $t" + else + echo "Error: Test file $full_path, $full_path.sh, or $full_path.pl does not exist!" + exit 1 + fi + done + # trim it + SPECIFIC_TESTS=$(echo $SPECIFIC_TESTS | xargs) + echo "Running specific tests: $SPECIFIC_TESTS" + fi fi # * timeout used to kill occasionally errant/"stuck" processes (note: 'release' testing takes ~1 hour; 'debug' testing takes ~2.5 hours) From 99120d32c13f01b6e146b1f5546c5e2520d73a16 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 24 Sep 2023 23:13:34 +0200 Subject: [PATCH 0131/2851] cut: fail when the input == usize::MAX --- src/uucore/src/lib/features/ranges.rs | 2 ++ tests/by-util/test_cut.rs | 8 ++++++++ 2 files changed, 10 insertions(+) diff --git a/src/uucore/src/lib/features/ranges.rs b/src/uucore/src/lib/features/ranges.rs index 29f40218363..7c09f922d93 100644 --- a/src/uucore/src/lib/features/ranges.rs +++ b/src/uucore/src/lib/features/ranges.rs @@ -38,6 +38,8 @@ impl FromStr for Range { fn parse(s: &str) -> Result { match s.parse::() { Ok(0) => Err("fields and positions are numbered from 1"), + // GNU fails when we are at the limit. Match their behavior + Ok(n) if n == usize::MAX => Err("byte/character offset is too large"), Ok(n) => Ok(n), Err(_) => Err("failed to parse range"), } diff --git a/tests/by-util/test_cut.rs b/tests/by-util/test_cut.rs index 7f86c803e8f..184e413a867 100644 --- a/tests/by-util/test_cut.rs +++ b/tests/by-util/test_cut.rs @@ -117,6 +117,14 @@ fn test_whitespace_with_char() { .code_is(1); } +#[test] +fn test_too_large() { + new_ucmd!() + .args(&["-b1-18446744073709551615", "/dev/null"]) + .fails() + .code_is(1); +} + #[test] fn test_specify_delimiter() { for param in ["-d", "--delimiter", "--del"] { From 265c25871385eaf33c1945609ae5cf6521a790f3 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 24 Sep 2023 23:23:27 +0200 Subject: [PATCH 0132/2851] ranges: add unit tests to verify the parsing To test them: $ cargo test -p uucore --features ranges --- src/uucore/src/lib/features/ranges.rs | 30 +++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/uucore/src/lib/features/ranges.rs b/src/uucore/src/lib/features/ranges.rs index 7c09f922d93..19ba23fb277 100644 --- a/src/uucore/src/lib/features/ranges.rs +++ b/src/uucore/src/lib/features/ranges.rs @@ -161,6 +161,7 @@ pub fn contain(ranges: &[Range], n: usize) -> bool { #[cfg(test)] mod test { use super::{complement, Range}; + use std::str::FromStr; fn m(a: Vec, b: &[Range]) { assert_eq!(Range::merge(a), b); @@ -231,4 +232,33 @@ mod test { // With start and end assert_eq!(complement(&[r(1, 4), r(6, usize::MAX - 1)]), vec![r(5, 5)]); } + + #[test] + fn test_from_str() { + assert_eq!(Range::from_str("5"), Ok(Range { low: 5, high: 5 })); + assert_eq!(Range::from_str("3-5"), Ok(Range { low: 3, high: 5 })); + assert_eq!( + Range::from_str("5-3"), + Err("high end of range less than low end") + ); + assert_eq!(Range::from_str("-"), Err("invalid range with no endpoint")); + assert_eq!( + Range::from_str("3-"), + Ok(Range { + low: 3, + high: usize::MAX - 1 + }) + ); + assert_eq!(Range::from_str("-5"), Ok(Range { low: 1, high: 5 })); + assert_eq!( + Range::from_str("0"), + Err("fields and positions are numbered from 1") + ); + + let max_value = format!("{}", usize::MAX); + assert_eq!( + Range::from_str(&max_value), + Err("byte/character offset is too large") + ); + } } From 2a97eab685786f7a81dea294cfbac199d4848957 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 26 Sep 2023 07:45:23 +0000 Subject: [PATCH 0133/2851] chore(deps): update rust crate exacl to 0.11.0 --- Cargo.lock | 16 ++++++++-------- Cargo.toml | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 53e3232fc74..24ef766d019 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -149,9 +149,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.3.3" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "630be753d4e58660abd17930c71b647fe46c27ea6b63cc59e1e3851406972e42" +checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" [[package]] name = "blake2b_simd" @@ -640,7 +640,7 @@ version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df" dependencies = [ - "bitflags 2.3.3", + "bitflags 2.4.0", "crossterm_winapi", "libc", "mio", @@ -805,11 +805,11 @@ dependencies = [ [[package]] name = "exacl" -version = "0.10.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cfeb22a59deb24c3262c43ffcafd1eb807180f371f9fcc99098d181b5d639be" +checksum = "c695152c1c2777163ea93fff517edc6dd1f8fc226c14b0d60cdcde0beb316d9f" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.4.0", "log", "scopeguard", "uuid", @@ -1340,7 +1340,7 @@ version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" dependencies = [ - "bitflags 2.3.3", + "bitflags 2.4.0", "cfg-if", "libc", ] @@ -1876,7 +1876,7 @@ version = "0.38.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19ed4fa021d81c8392ce04db050a3da9a60299050b7ae1cf482d862b54a7218f" dependencies = [ - "bitflags 2.3.3", + "bitflags 2.4.0", "errno", "libc", "linux-raw-sys 0.4.5", diff --git a/Cargo.toml b/Cargo.toml index 15351798d9f..2ee4444b184 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -275,7 +275,7 @@ compare = "0.1.0" coz = { version = "0.1.3" } crossterm = ">=0.27.0" ctrlc = { version = "3.4", features = ["termination"] } -exacl = "0.10.0" +exacl = "0.11.0" file_diff = "1.0.0" filetime = "0.2" fnv = "1.0.7" From ff500d7d6f51fa64e6ba2b526c4fd1b5748f9850 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Tue, 26 Sep 2023 16:12:59 +0200 Subject: [PATCH 0134/2851] expr: interpret numbers != 0 as true for | and & --- src/uu/expr/src/syntax_tree.rs | 6 +++--- tests/by-util/test_expr.rs | 17 +++++++++++++++++ 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/src/uu/expr/src/syntax_tree.rs b/src/uu/expr/src/syntax_tree.rs index 0e0795bd4fb..b3cd329ba6e 100644 --- a/src/uu/expr/src/syntax_tree.rs +++ b/src/uu/expr/src/syntax_tree.rs @@ -8,10 +8,10 @@ //! * `` //! -// spell-checker:ignore (ToDO) binop binops ints paren prec multibytes +// spell-checker:ignore (ToDO) ints paren prec multibytes use num_bigint::BigInt; -use num_traits::{One, Zero}; +use num_traits::Zero; use onig::{Regex, RegexOptions, Syntax}; use crate::tokens::Token; @@ -515,7 +515,7 @@ fn value_as_bool(s: &str) -> bool { return false; } match s.parse::() { - Ok(n) => n.is_one(), + Ok(n) => n != Zero::zero(), Err(_) => true, } } diff --git a/tests/by-util/test_expr.rs b/tests/by-util/test_expr.rs index 665f89615e4..ea5a964d999 100644 --- a/tests/by-util/test_expr.rs +++ b/tests/by-util/test_expr.rs @@ -113,6 +113,16 @@ fn test_or() { .args(&["foo", "|", "bar"]) .succeeds() .stdout_only("foo\n"); + + new_ucmd!() + .args(&["14", "|", "1"]) + .succeeds() + .stdout_only("14\n"); + + new_ucmd!() + .args(&["-14", "|", "1"]) + .succeeds() + .stdout_only("-14\n"); } #[test] @@ -123,6 +133,13 @@ fn test_and() { .stdout_only("foo\n"); new_ucmd!().args(&["", "&", "1"]).run().stdout_is("0\n"); + + new_ucmd!().args(&["14", "&", "1"]).run().stdout_is("14\n"); + + new_ucmd!() + .args(&["-14", "&", "1"]) + .run() + .stdout_is("-14\n"); } #[test] From 6c7a20fc1868bf636b2c331bc1fcca39983d810b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 26 Sep 2023 16:57:54 +0000 Subject: [PATCH 0135/2851] chore(deps): update rust crate sha2 to 0.10.8 --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 24ef766d019..c007551987f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1955,9 +1955,9 @@ dependencies = [ [[package]] name = "sha2" -version = "0.10.7" +version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479fb9d862239e610720565ca91403019f2f00410f1864c5aa7479b950a76ed8" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" dependencies = [ "cfg-if", "cpufeatures", diff --git a/Cargo.toml b/Cargo.toml index 2ee4444b184..a40d066d4ef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -336,7 +336,7 @@ zip = { version = "0.6.6", default_features = false, features = ["deflate"] } hex = "0.4.3" md-5 = "0.10.6" sha1 = "0.10.6" -sha2 = "0.10.7" +sha2 = "0.10.8" sha3 = "0.10.8" blake2b_simd = "1.0.2" blake3 = "1.5.0" From 2789885648053e1ff1fe9c745dddefe0a5f2840b Mon Sep 17 00:00:00 2001 From: tommady Date: Wed, 27 Sep 2023 19:16:10 +0800 Subject: [PATCH 0136/2851] cp: fix cp -dR --no-preserve=links d c should have different inodes (#5320) * fix issue 5308 --- src/uu/cp/src/cp.rs | 38 ++++++++++++++++++++++++-------------- tests/by-util/test_cp.rs | 2 +- 2 files changed, 25 insertions(+), 15 deletions(-) diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index 05261c22fe2..b6270719cc9 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -931,23 +931,33 @@ impl Options { }; // Parse attributes to preserve - let attributes = if let Some(attribute_strs) = matches.get_many::(options::PRESERVE) - { - if attribute_strs.len() == 0 { + let mut attributes = + if let Some(attribute_strs) = matches.get_many::(options::PRESERVE) { + if attribute_strs.len() == 0 { + Attributes::DEFAULT + } else { + Attributes::parse_iter(attribute_strs)? + } + } else if matches.get_flag(options::ARCHIVE) { + // --archive is used. Same as --preserve=all + Attributes::ALL + } else if matches.get_flag(options::NO_DEREFERENCE_PRESERVE_LINKS) { + Attributes::LINKS + } else if matches.get_flag(options::PRESERVE_DEFAULT_ATTRIBUTES) { Attributes::DEFAULT } else { - Attributes::parse_iter(attribute_strs)? + Attributes::NONE + }; + + // handling no-preserve options and adjusting the attributes + if let Some(attribute_strs) = matches.get_many::(options::NO_PRESERVE) { + if attribute_strs.len() > 0 { + let no_preserve_attributes = Attributes::parse_iter(attribute_strs)?; + if matches!(no_preserve_attributes.links, Preserve::Yes { .. }) { + attributes.links = Preserve::No; + } } - } else if matches.get_flag(options::ARCHIVE) { - // --archive is used. Same as --preserve=all - Attributes::ALL - } else if matches.get_flag(options::NO_DEREFERENCE_PRESERVE_LINKS) { - Attributes::LINKS - } else if matches.get_flag(options::PRESERVE_DEFAULT_ATTRIBUTES) { - Attributes::DEFAULT - } else { - Attributes::NONE - }; + } #[cfg(not(feature = "feat_selinux"))] if let Preserve::Yes { required } = attributes.context { diff --git a/tests/by-util/test_cp.rs b/tests/by-util/test_cp.rs index afac4cd3a6c..1ce74572d5d 100644 --- a/tests/by-util/test_cp.rs +++ b/tests/by-util/test_cp.rs @@ -1494,7 +1494,7 @@ fn test_cp_preserve_links_case_5() { let metadata_a = std::fs::metadata(at.subdir.join("c").join("a")).unwrap(); let metadata_b = std::fs::metadata(at.subdir.join("c").join("b")).unwrap(); - assert_eq!(metadata_a.ino(), metadata_b.ino()); + assert_ne!(metadata_a.ino(), metadata_b.ino()); } } From aea986948df6375a90b3963e6c930f3a1adfce45 Mon Sep 17 00:00:00 2001 From: KAA the Wise Date: Wed, 27 Sep 2023 23:05:29 +0300 Subject: [PATCH 0137/2851] mkdir: make `mkdir` public and document it - Made the `mkdir` function public. Added documentation, describing the behavior and parameters - Moved the GNU dot-stripping behavior from `exec` to `mkdir`. --- src/uu/mkdir/src/mkdir.rs | 35 ++++++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/src/uu/mkdir/src/mkdir.rs b/src/uu/mkdir/src/mkdir.rs index 2044855e45e..354335d8c30 100644 --- a/src/uu/mkdir/src/mkdir.rs +++ b/src/uu/mkdir/src/mkdir.rs @@ -141,21 +141,34 @@ pub fn uu_app() -> Command { */ fn exec(dirs: ValuesRef, recursive: bool, mode: u32, verbose: bool) -> UResult<()> { for dir in dirs { - // Special case to match GNU's behavior: - // mkdir -p foo/. should work and just create foo/ - // std::fs::create_dir("foo/."); fails in pure Rust - let path = if recursive { - dir_strip_dot_for_creation(&PathBuf::from(dir)) - } else { - // Normal case - PathBuf::from(dir) - }; - show_if_err!(mkdir(path.as_path(), recursive, mode, verbose)); + let path_buf = PathBuf::from(dir); + let path = path_buf.as_path(); + + show_if_err!(mkdir(path, recursive, mode, verbose)); } Ok(()) } -fn mkdir(path: &Path, recursive: bool, mode: u32, verbose: bool) -> UResult<()> { +/// Create directory at a given `path`. +/// +/// ## Options +/// +/// * `recursive` --- create parent directories for the `path`, if they do not +/// exist. +/// * `mode` --- file mode for the directories (not implemented on windows). +/// * `verbose` --- print a message for each printed directory. +/// +/// ## Trailing dot +/// +/// To match the GNU behavior, a path with the last directory being a single dot +/// (like `some/path/to/.`) is created (with the dot stripped). +pub fn mkdir(path: &Path, recursive: bool, mode: u32, verbose: bool) -> UResult<()> { + // Special case to match GNU's behavior: + // mkdir -p foo/. should work and just create foo/ + // std::fs::create_dir("foo/."); fails in pure Rust + let path_buf = dir_strip_dot_for_creation(path); + let path = path_buf.as_path(); + create_dir(path, recursive, verbose, false)?; chmod(path, mode) } From 9c0b9cae5610a81c2c9d152edb7fcca1c77e83dd Mon Sep 17 00:00:00 2001 From: John Shin Date: Wed, 27 Sep 2023 14:55:08 -0700 Subject: [PATCH 0138/2851] seq: simplyfy nan and inf parsing --- src/uu/seq/src/numberparse.rs | 29 +++++++---------------------- 1 file changed, 7 insertions(+), 22 deletions(-) diff --git a/src/uu/seq/src/numberparse.rs b/src/uu/seq/src/numberparse.rs index 156f80fb91f..07d853d586c 100644 --- a/src/uu/seq/src/numberparse.rs +++ b/src/uu/seq/src/numberparse.rs @@ -69,28 +69,13 @@ fn parse_no_decimal_no_exponent(s: &str) -> Result { // Possibly "NaN" or "inf". - if let Ok(num) = f32::from_str(s) { - // pattern matching on floating point literal is not encouraged 'https://github.com/rust-lang/rust/issues/41620' - if num == f32::INFINITY { - Ok(PreciseNumber::new( - Number::Float(ExtendedBigDecimal::Infinity), - 0, - 0, - )) - } else if num == f32::NEG_INFINITY { - Ok(PreciseNumber::new( - Number::Float(ExtendedBigDecimal::MinusInfinity), - 0, - 0, - )) - } else if num.is_nan() { - Err(ParseNumberError::Nan) - } else { - Err(ParseNumberError::Float) - } - } else { - Err(ParseNumberError::Float) - } + let float_val = match s.to_ascii_lowercase().as_str() { + "inf" | "infinity" => ExtendedBigDecimal::Infinity, + "-inf" | "-infinity" => ExtendedBigDecimal::MinusInfinity, + "nan" | "-nan" => return Err(ParseNumberError::Nan), + _ => return Err(ParseNumberError::Float), + }; + Ok(PreciseNumber::new(Number::Float(float_val), 0, 0)) } } } From f0f64bd3b728c905944aaf3534f618d85ded30c6 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Thu, 28 Sep 2023 08:42:30 +0200 Subject: [PATCH 0139/2851] fuzz: move the common duplicated code into a function --- fuzz/fuzz_targets/fuzz_common.rs | 47 ++++++++++++++++++++++++ fuzz/fuzz_targets/fuzz_expr.rs | 49 ++----------------------- fuzz/fuzz_targets/fuzz_test.rs | 61 ++++++-------------------------- 3 files changed, 59 insertions(+), 98 deletions(-) diff --git a/fuzz/fuzz_targets/fuzz_common.rs b/fuzz/fuzz_targets/fuzz_common.rs index fb1f498e90b..a33d603f130 100644 --- a/fuzz/fuzz_targets/fuzz_common.rs +++ b/fuzz/fuzz_targets/fuzz_common.rs @@ -3,6 +3,8 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. +use libc::{dup, dup2, STDOUT_FILENO}; +use std::ffi::OsString; use std::process::Command; use std::sync::atomic::Ordering; use std::sync::{atomic::AtomicBool, Once}; @@ -28,3 +30,48 @@ pub fn is_gnu_cmd(cmd_path: &str) -> Result<(), std::io::Error> { panic!("Not the GNU implementation"); } } + +pub fn generate_and_run_uumain(args: &mut Vec, uumain_function: F) -> (String, i32) +where + F: FnOnce(std::vec::IntoIter) -> i32, +{ + let uumain_exit_status; + + let original_stdout_fd = unsafe { dup(STDOUT_FILENO) }; + println!("Running test {:?}", &args[1..]); + let mut pipe_fds = [-1; 2]; + unsafe { libc::pipe(pipe_fds.as_mut_ptr()) }; + + { + unsafe { dup2(pipe_fds[1], STDOUT_FILENO) }; + uumain_exit_status = uumain_function(args.clone().into_iter()); + unsafe { dup2(original_stdout_fd, STDOUT_FILENO) }; + unsafe { libc::close(original_stdout_fd) }; + } + unsafe { libc::close(pipe_fds[1]) }; + + let mut captured_output = Vec::new(); + let mut read_buffer = [0; 1024]; + loop { + let bytes_read = unsafe { + libc::read( + pipe_fds[0], + read_buffer.as_mut_ptr() as *mut libc::c_void, + read_buffer.len(), + ) + }; + if bytes_read <= 0 { + break; + } + captured_output.extend_from_slice(&read_buffer[..bytes_read as usize]); + } + + unsafe { libc::close(pipe_fds[0]) }; + + let my_output = String::from_utf8_lossy(&captured_output) + .to_string() + .trim() + .to_owned(); + + (my_output, uumain_exit_status) +} diff --git a/fuzz/fuzz_targets/fuzz_expr.rs b/fuzz/fuzz_targets/fuzz_expr.rs index e364342b8bc..28fded99e07 100644 --- a/fuzz/fuzz_targets/fuzz_expr.rs +++ b/fuzz/fuzz_targets/fuzz_expr.rs @@ -12,9 +12,9 @@ use rand::seq::SliceRandom; use rand::Rng; use std::ffi::OsString; -use libc::{dup, dup2, STDOUT_FILENO}; use std::process::Command; mod fuzz_common; +use crate::fuzz_common::generate_and_run_uumain; use crate::fuzz_common::is_gnu_cmd; static CMD_PATH: &str = "expr"; @@ -108,52 +108,7 @@ fuzz_target!(|_data: &[u8]| { let mut args = vec![OsString::from("expr")]; args.extend(expr.split_whitespace().map(OsString::from)); - // Save the original stdout file descriptor - let original_stdout_fd = unsafe { dup(STDOUT_FILENO) }; - - // Create a pipe to capture stdout - let mut pipe_fds = [-1; 2]; - unsafe { libc::pipe(pipe_fds.as_mut_ptr()) }; - let uumain_exit_code; - { - // Redirect stdout to the write end of the pipe - unsafe { dup2(pipe_fds[1], STDOUT_FILENO) }; - - // Run uumain with the provided arguments - uumain_exit_code = uumain(args.clone().into_iter()); - - // Restore original stdout - unsafe { dup2(original_stdout_fd, STDOUT_FILENO) }; - unsafe { libc::close(original_stdout_fd) }; - } - // Close the write end of the pipe - unsafe { libc::close(pipe_fds[1]) }; - - // Read captured output from the read end of the pipe - let mut captured_output = Vec::new(); - let mut read_buffer = [0; 1024]; - loop { - let bytes_read = unsafe { - libc::read( - pipe_fds[0], - read_buffer.as_mut_ptr() as *mut libc::c_void, - read_buffer.len(), - ) - }; - if bytes_read <= 0 { - break; - } - captured_output.extend_from_slice(&read_buffer[..bytes_read as usize]); - } - - // Close the read end of the pipe - unsafe { libc::close(pipe_fds[0]) }; - - // Convert captured output to a string - let rust_output = String::from_utf8_lossy(&captured_output) - .to_string() - .trim() - .to_owned(); + let (rust_output, uumain_exit_code) = generate_and_run_uumain(&mut args, uumain); // Run GNU expr with the provided arguments and compare the output match run_gnu_expr(&args[1..]) { diff --git a/fuzz/fuzz_targets/fuzz_test.rs b/fuzz/fuzz_targets/fuzz_test.rs index 537e21abda5..535696b45dc 100644 --- a/fuzz/fuzz_targets/fuzz_test.rs +++ b/fuzz/fuzz_targets/fuzz_test.rs @@ -12,9 +12,11 @@ use rand::seq::SliceRandom; use rand::Rng; use std::ffi::OsString; -use libc::{dup, dup2, STDOUT_FILENO}; use std::process::Command; +mod fuzz_common; +use crate::fuzz_common::generate_and_run_uumain; + #[derive(PartialEq, Debug, Clone)] enum ArgType { STRING, @@ -26,8 +28,11 @@ enum ArgType { // Add any other types as needed } +static CMD_PATH: &str = "/usr/bin/test"; + fn run_gnu_test(args: &[OsString]) -> Result<(String, i32), std::io::Error> { - let mut command = Command::new("test"); + let mut command = Command::new(CMD_PATH); + for arg in args { command.arg(arg); } @@ -210,58 +215,12 @@ fuzz_target!(|_data: &[u8]| { let mut rng = rand::thread_rng(); let max_args = rng.gen_range(1..=6); let mut args = vec![OsString::from("test")]; - let uumain_exit_status; for _ in 0..max_args { args.push(OsString::from(generate_test_arg())); } - // Save the original stdout file descriptor - let original_stdout_fd = unsafe { dup(STDOUT_FILENO) }; - println!("Running test {:?}", &args[1..]); - // Create a pipe to capture stdout - let mut pipe_fds = [-1; 2]; - unsafe { libc::pipe(pipe_fds.as_mut_ptr()) }; - - { - // Redirect stdout to the write end of the pipe - unsafe { dup2(pipe_fds[1], STDOUT_FILENO) }; - - // Run uumain with the provided arguments - uumain_exit_status = uumain(args.clone().into_iter()); - - // Restore original stdout - unsafe { dup2(original_stdout_fd, STDOUT_FILENO) }; - unsafe { libc::close(original_stdout_fd) }; - } - // Close the write end of the pipe - unsafe { libc::close(pipe_fds[1]) }; - - // Read captured output from the read end of the pipe - let mut captured_output = Vec::new(); - let mut read_buffer = [0; 1024]; - loop { - let bytes_read = unsafe { - libc::read( - pipe_fds[0], - read_buffer.as_mut_ptr() as *mut libc::c_void, - read_buffer.len(), - ) - }; - if bytes_read <= 0 { - break; - } - captured_output.extend_from_slice(&read_buffer[..bytes_read as usize]); - } - - // Close the read end of the pipe - unsafe { libc::close(pipe_fds[0]) }; - - // Convert captured output to a string - let my_output = String::from_utf8_lossy(&captured_output) - .to_string() - .trim() - .to_owned(); + let (rust_output, uumain_exit_status) = generate_and_run_uumain(&mut args, uumain); // Run GNU test with the provided arguments and compare the output match run_gnu_test(&args[1..]) { @@ -269,10 +228,10 @@ fuzz_target!(|_data: &[u8]| { let gnu_output = gnu_output.trim().to_owned(); println!("gnu_exit_status {}", gnu_exit_status); println!("uumain_exit_status {}", uumain_exit_status); - if my_output != gnu_output || uumain_exit_status != gnu_exit_status { + if rust_output != gnu_output || uumain_exit_status != gnu_exit_status { println!("Discrepancy detected!"); println!("Test: {:?}", &args[1..]); - println!("My output: {}", my_output); + println!("My output: {}", rust_output); println!("GNU output: {}", gnu_output); println!("My exit status: {}", uumain_exit_status); println!("GNU exit status: {}", gnu_exit_status); From 51eb20a15d768b0b24b60a78b98c27602ed5cc50 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Thu, 28 Sep 2023 10:50:56 +0200 Subject: [PATCH 0140/2851] fmt: use clap's value parser for goal & width --- src/uu/fmt/src/fmt.rs | 30 ++++++++---------------------- tests/by-util/test_fmt.rs | 22 ++++++++++++++++++++++ 2 files changed, 30 insertions(+), 22 deletions(-) diff --git a/src/uu/fmt/src/fmt.rs b/src/uu/fmt/src/fmt.rs index c5eac707303..c30d923b76b 100644 --- a/src/uu/fmt/src/fmt.rs +++ b/src/uu/fmt/src/fmt.rs @@ -131,16 +131,8 @@ fn parse_arguments(args: impl uucore::Args) -> UResult<(Vec, FmtOptions) fmt_opts.use_anti_prefix = true; }; - if let Some(s) = matches.get_one::(OPT_WIDTH) { - fmt_opts.width = match s.parse::() { - Ok(t) => t, - Err(e) => { - return Err(USimpleError::new( - 1, - format!("Invalid WIDTH specification: {}: {}", s.quote(), e), - )); - } - }; + if let Some(width) = matches.get_one::(OPT_WIDTH) { + fmt_opts.width = *width; if fmt_opts.width > MAX_WIDTH { return Err(USimpleError::new( 1, @@ -156,16 +148,8 @@ fn parse_arguments(args: impl uucore::Args) -> UResult<(Vec, FmtOptions) ); }; - if let Some(s) = matches.get_one::(OPT_GOAL) { - fmt_opts.goal = match s.parse::() { - Ok(t) => t, - Err(e) => { - return Err(USimpleError::new( - 1, - format!("Invalid GOAL specification: {}: {}", s.quote(), e), - )); - } - }; + if let Some(goal) = matches.get_one::(OPT_GOAL) { + fmt_opts.goal = *goal; if !matches.contains_id(OPT_WIDTH) { fmt_opts.width = cmp::max( fmt_opts.goal * 100 / DEFAULT_GOAL_TO_WIDTH_RATIO, @@ -372,14 +356,16 @@ pub fn uu_app() -> Command { .short('w') .long("width") .help("Fill output lines up to a maximum of WIDTH columns, default 75.") - .value_name("WIDTH"), + .value_name("WIDTH") + .value_parser(clap::value_parser!(usize)), ) .arg( Arg::new(OPT_GOAL) .short('g') .long("goal") .help("Goal width, default of 93% of WIDTH. Must be less than WIDTH.") - .value_name("GOAL"), + .value_name("GOAL") + .value_parser(clap::value_parser!(usize)), ) .arg( Arg::new(OPT_QUICK) diff --git a/tests/by-util/test_fmt.rs b/tests/by-util/test_fmt.rs index 7d23cbd52f1..4fd05908019 100644 --- a/tests/by-util/test_fmt.rs +++ b/tests/by-util/test_fmt.rs @@ -48,6 +48,17 @@ fn test_fmt_width_too_big() { } } +#[test] +fn test_fmt_invalid_width() { + for param in ["-w", "--width"] { + new_ucmd!() + .args(&["one-word-per-line.txt", param, "invalid"]) + .fails() + .code_is(1) + .stderr_contains("invalid value 'invalid'"); + } +} + #[ignore] #[test] fn test_fmt_goal() { @@ -70,6 +81,17 @@ fn test_fmt_goal_too_big() { } } +#[test] +fn test_fmt_invalid_goal() { + for param in ["-g", "--goal"] { + new_ucmd!() + .args(&["one-word-per-line.txt", param, "invalid"]) + .fails() + .code_is(1) + .stderr_contains("invalid value 'invalid'"); + } +} + #[test] fn test_fmt_set_goal_not_contain_width() { for param in ["-g", "--goal"] { From a17ede9ef02ca864dc7fcd5bda681e990339b674 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Thu, 28 Sep 2023 15:36:06 +0200 Subject: [PATCH 0141/2851] Remove the full path to test --- fuzz/fuzz_targets/fuzz_test.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fuzz/fuzz_targets/fuzz_test.rs b/fuzz/fuzz_targets/fuzz_test.rs index 535696b45dc..dbd2db54ab0 100644 --- a/fuzz/fuzz_targets/fuzz_test.rs +++ b/fuzz/fuzz_targets/fuzz_test.rs @@ -28,7 +28,7 @@ enum ArgType { // Add any other types as needed } -static CMD_PATH: &str = "/usr/bin/test"; +static CMD_PATH: &str = "test"; fn run_gnu_test(args: &[OsString]) -> Result<(String, i32), std::io::Error> { let mut command = Command::new(CMD_PATH); From e6f9e358d4f3365ea62ef86ddbe5b5b3f71a3dbf Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Thu, 28 Sep 2023 21:48:34 +0200 Subject: [PATCH 0142/2851] fuzz: create a function run_gnu_cmd to deduplicate the code --- fuzz/fuzz_targets/fuzz_common.rs | 30 ++++++++++++++++++++++++++++++ fuzz/fuzz_targets/fuzz_expr.rs | 28 ++-------------------------- fuzz/fuzz_targets/fuzz_test.rs | 20 ++------------------ 3 files changed, 34 insertions(+), 44 deletions(-) diff --git a/fuzz/fuzz_targets/fuzz_common.rs b/fuzz/fuzz_targets/fuzz_common.rs index a33d603f130..351a7e8e8af 100644 --- a/fuzz/fuzz_targets/fuzz_common.rs +++ b/fuzz/fuzz_targets/fuzz_common.rs @@ -5,6 +5,7 @@ use libc::{dup, dup2, STDOUT_FILENO}; use std::ffi::OsString; +use std::io; use std::process::Command; use std::sync::atomic::Ordering; use std::sync::{atomic::AtomicBool, Once}; @@ -75,3 +76,32 @@ where (my_output, uumain_exit_status) } + +pub fn run_gnu_cmd( + cmd_path: &str, + args: &[OsString], + check_gnu: bool, +) -> Result<(String, i32), io::Error> { + if check_gnu { + is_gnu_cmd(cmd_path)?; // Check if it's a GNU implementation + } + + let mut command = Command::new(cmd_path); + for arg in args { + command.arg(arg); + } + + let output = command.output()?; + let exit_code = output.status.code().unwrap_or(-1); + if output.status.success() || !check_gnu { + Ok(( + String::from_utf8_lossy(&output.stdout).to_string(), + exit_code, + )) + } else { + Err(io::Error::new( + io::ErrorKind::Other, + format!("GNU command execution failed with exit code {}", exit_code), + )) + } +} diff --git a/fuzz/fuzz_targets/fuzz_expr.rs b/fuzz/fuzz_targets/fuzz_expr.rs index 28fded99e07..6344c052575 100644 --- a/fuzz/fuzz_targets/fuzz_expr.rs +++ b/fuzz/fuzz_targets/fuzz_expr.rs @@ -12,35 +12,11 @@ use rand::seq::SliceRandom; use rand::Rng; use std::ffi::OsString; -use std::process::Command; mod fuzz_common; -use crate::fuzz_common::generate_and_run_uumain; -use crate::fuzz_common::is_gnu_cmd; +use crate::fuzz_common::{generate_and_run_uumain, run_gnu_cmd}; static CMD_PATH: &str = "expr"; -fn run_gnu_expr(args: &[OsString]) -> Result<(String, i32), std::io::Error> { - is_gnu_cmd(CMD_PATH)?; // Check if it's a GNU implementation - - let mut command = Command::new(CMD_PATH); - for arg in args { - command.arg(arg); - } - let output = command.output()?; - let exit_code = output.status.code().unwrap_or(-1); - if output.status.success() { - Ok(( - String::from_utf8_lossy(&output.stdout).to_string(), - exit_code, - )) - } else { - Err(std::io::Error::new( - std::io::ErrorKind::Other, - format!("GNU expr execution failed with exit code {}", exit_code), - )) - } -} - fn generate_random_string(max_length: usize) -> String { let mut rng = rand::thread_rng(); let valid_utf8: Vec = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" @@ -111,7 +87,7 @@ fuzz_target!(|_data: &[u8]| { let (rust_output, uumain_exit_code) = generate_and_run_uumain(&mut args, uumain); // Run GNU expr with the provided arguments and compare the output - match run_gnu_expr(&args[1..]) { + match run_gnu_cmd(CMD_PATH, &args[1..], true) { Ok((gnu_output, gnu_exit_code)) => { let gnu_output = gnu_output.trim().to_owned(); if uumain_exit_code != gnu_exit_code { diff --git a/fuzz/fuzz_targets/fuzz_test.rs b/fuzz/fuzz_targets/fuzz_test.rs index dbd2db54ab0..bfde25246d9 100644 --- a/fuzz/fuzz_targets/fuzz_test.rs +++ b/fuzz/fuzz_targets/fuzz_test.rs @@ -12,10 +12,8 @@ use rand::seq::SliceRandom; use rand::Rng; use std::ffi::OsString; -use std::process::Command; - mod fuzz_common; -use crate::fuzz_common::generate_and_run_uumain; +use crate::fuzz_common::{generate_and_run_uumain, run_gnu_cmd}; #[derive(PartialEq, Debug, Clone)] enum ArgType { @@ -30,20 +28,6 @@ enum ArgType { static CMD_PATH: &str = "test"; -fn run_gnu_test(args: &[OsString]) -> Result<(String, i32), std::io::Error> { - let mut command = Command::new(CMD_PATH); - - for arg in args { - command.arg(arg); - } - let output = command.output()?; - let exit_status = output.status.code().unwrap_or(-1); // Capture the exit status code - Ok(( - String::from_utf8_lossy(&output.stdout).to_string(), - exit_status, - )) -} - fn generate_random_string(max_length: usize) -> String { let mut rng = rand::thread_rng(); let valid_utf8: Vec = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" @@ -223,7 +207,7 @@ fuzz_target!(|_data: &[u8]| { let (rust_output, uumain_exit_status) = generate_and_run_uumain(&mut args, uumain); // Run GNU test with the provided arguments and compare the output - match run_gnu_test(&args[1..]) { + match run_gnu_cmd(CMD_PATH, &args[1..], false) { Ok((gnu_output, gnu_exit_status)) => { let gnu_output = gnu_output.trim().to_owned(); println!("gnu_exit_status {}", gnu_exit_status); From a576054d42373ee3e502f015ffb998cc232df824 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Thu, 28 Sep 2023 21:52:26 +0200 Subject: [PATCH 0143/2851] fuzz: fix clippy warnings --- fuzz/fuzz_targets/fuzz_common.rs | 4 ++-- fuzz/fuzz_targets/fuzz_date.rs | 2 +- fuzz/fuzz_targets/fuzz_expr.rs | 14 +++++++------- fuzz/fuzz_targets/fuzz_parse_glob.rs | 2 +- fuzz/fuzz_targets/fuzz_test.rs | 4 ++-- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/fuzz/fuzz_targets/fuzz_common.rs b/fuzz/fuzz_targets/fuzz_common.rs index 351a7e8e8af..a94963ef024 100644 --- a/fuzz/fuzz_targets/fuzz_common.rs +++ b/fuzz/fuzz_targets/fuzz_common.rs @@ -32,7 +32,7 @@ pub fn is_gnu_cmd(cmd_path: &str) -> Result<(), std::io::Error> { } } -pub fn generate_and_run_uumain(args: &mut Vec, uumain_function: F) -> (String, i32) +pub fn generate_and_run_uumain(args: &[OsString], uumain_function: F) -> (String, i32) where F: FnOnce(std::vec::IntoIter) -> i32, { @@ -45,7 +45,7 @@ where { unsafe { dup2(pipe_fds[1], STDOUT_FILENO) }; - uumain_exit_status = uumain_function(args.clone().into_iter()); + uumain_exit_status = uumain_function(args.to_owned().into_iter()); unsafe { dup2(original_stdout_fd, STDOUT_FILENO) }; unsafe { libc::close(original_stdout_fd) }; } diff --git a/fuzz/fuzz_targets/fuzz_date.rs b/fuzz/fuzz_targets/fuzz_date.rs index 96c56cc6bf2..0f9cb262c06 100644 --- a/fuzz/fuzz_targets/fuzz_date.rs +++ b/fuzz/fuzz_targets/fuzz_date.rs @@ -9,6 +9,6 @@ fuzz_target!(|data: &[u8]| { let args = data .split(|b| *b == delim) .filter_map(|e| std::str::from_utf8(e).ok()) - .map(|e| OsString::from(e)); + .map(OsString::from); uumain(args); }); diff --git a/fuzz/fuzz_targets/fuzz_expr.rs b/fuzz/fuzz_targets/fuzz_expr.rs index 6344c052575..fb7b1730956 100644 --- a/fuzz/fuzz_targets/fuzz_expr.rs +++ b/fuzz/fuzz_targets/fuzz_expr.rs @@ -84,7 +84,7 @@ fuzz_target!(|_data: &[u8]| { let mut args = vec![OsString::from("expr")]; args.extend(expr.split_whitespace().map(OsString::from)); - let (rust_output, uumain_exit_code) = generate_and_run_uumain(&mut args, uumain); + let (rust_output, uumain_exit_code) = generate_and_run_uumain(&args, uumain); // Run GNU expr with the provided arguments and compare the output match run_gnu_cmd(CMD_PATH, &args[1..], true) { @@ -96,16 +96,16 @@ fuzz_target!(|_data: &[u8]| { println!("GNU code: {}", gnu_exit_code); panic!("Different error codes"); } - if rust_output != gnu_output { - println!("Expression: {}", expr); - println!("Rust output: {}", rust_output); - println!("GNU output: {}", gnu_output); - panic!("Different output between Rust & GNU"); - } else { + if rust_output == gnu_output { println!( "Outputs matched for expression: {} => Result: {}", expr, rust_output ); + } else { + println!("Expression: {}", expr); + println!("Rust output: {}", rust_output); + println!("GNU output: {}", gnu_output); + panic!("Different output between Rust & GNU"); } } Err(_) => { diff --git a/fuzz/fuzz_targets/fuzz_parse_glob.rs b/fuzz/fuzz_targets/fuzz_parse_glob.rs index 061569bc418..e235c0c9d89 100644 --- a/fuzz/fuzz_targets/fuzz_parse_glob.rs +++ b/fuzz/fuzz_targets/fuzz_parse_glob.rs @@ -5,6 +5,6 @@ use uucore::parse_glob; fuzz_target!(|data: &[u8]| { if let Ok(s) = std::str::from_utf8(data) { - _ = parse_glob::from_str(s) + _ = parse_glob::from_str(s); } }); diff --git a/fuzz/fuzz_targets/fuzz_test.rs b/fuzz/fuzz_targets/fuzz_test.rs index bfde25246d9..4805a41af16 100644 --- a/fuzz/fuzz_targets/fuzz_test.rs +++ b/fuzz/fuzz_targets/fuzz_test.rs @@ -142,7 +142,7 @@ fn generate_test_arg() -> String { 0 => { arg.push_str(&rng.gen_range(-100..=100).to_string()); } - 1 | 2 | 3 => { + 1..=3 => { let test_arg = test_args .choose(&mut rng) .expect("Failed to choose a random test argument"); @@ -204,7 +204,7 @@ fuzz_target!(|_data: &[u8]| { args.push(OsString::from(generate_test_arg())); } - let (rust_output, uumain_exit_status) = generate_and_run_uumain(&mut args, uumain); + let (rust_output, uumain_exit_status) = generate_and_run_uumain(&args, uumain); // Run GNU test with the provided arguments and compare the output match run_gnu_cmd(CMD_PATH, &args[1..], false) { From 55fd1f3617958209180b596c5e75fd017a2b1fb9 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Thu, 28 Sep 2023 21:25:38 +0200 Subject: [PATCH 0144/2851] Replace list of digit by is_ascii_digit and some ride along simplification --- src/uu/chmod/src/chmod.rs | 14 +++++------ src/uu/install/src/mode.rs | 5 +--- src/uu/mkdir/src/mkdir.rs | 36 +++++++++++++---------------- src/uu/mknod/src/parsemode.rs | 3 +-- src/uucore/src/lib/features/mode.rs | 3 +-- 5 files changed, 26 insertions(+), 35 deletions(-) diff --git a/src/uu/chmod/src/chmod.rs b/src/uu/chmod/src/chmod.rs index b007bb1d735..31663b1af9c 100644 --- a/src/uu/chmod/src/chmod.rs +++ b/src/uu/chmod/src/chmod.rs @@ -335,9 +335,7 @@ impl Chmoder { let mut new_mode = fperm; let mut naively_expected_new_mode = new_mode; for mode in cmode_unwrapped.split(',') { - // cmode is guaranteed to be Some in this case - let arr: &[char] = &['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']; - let result = if mode.contains(arr) { + let result = if mode.chars().any(|c| c.is_ascii_digit()) { mode::parse_numeric(new_mode, mode, file.is_dir()).map(|v| (v, v)) } else { mode::parse_symbolic(new_mode, mode, get_umask(), file.is_dir()).map(|m| { @@ -352,20 +350,22 @@ impl Chmoder { (m, naive_mode) }) }; + match result { Ok((mode, naive_mode)) => { new_mode = mode; naively_expected_new_mode = naive_mode; } Err(f) => { - if self.quiet { - return Err(ExitCode::new(1)); + return if self.quiet { + Err(ExitCode::new(1)) } else { - return Err(USimpleError::new(1, f)); - } + Err(USimpleError::new(1, f)) + }; } } } + self.change_file(fperm, new_mode, file)?; // if a permission would have been removed if umask was 0, but it wasn't because umask was not 0, print an error and fail if (new_mode & !naively_expected_new_mode) != 0 { diff --git a/src/uu/install/src/mode.rs b/src/uu/install/src/mode.rs index f9018e16f8e..ebdec14afe6 100644 --- a/src/uu/install/src/mode.rs +++ b/src/uu/install/src/mode.rs @@ -9,10 +9,7 @@ use uucore::mode; /// Takes a user-supplied string and tries to parse to u16 mode bitmask. pub fn parse(mode_string: &str, considering_dir: bool, umask: u32) -> Result { - let numbers: &[char] = &['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']; - - // Passing 000 as the existing permissions seems to mirror GNU behavior. - if mode_string.contains(numbers) { + if mode_string.chars().any(|c| c.is_ascii_digit()) { mode::parse_numeric(0, mode_string, considering_dir) } else { mode::parse_symbolic(0, mode_string, umask, considering_dir) diff --git a/src/uu/mkdir/src/mkdir.rs b/src/uu/mkdir/src/mkdir.rs index 2044855e45e..76aa51f079c 100644 --- a/src/uu/mkdir/src/mkdir.rs +++ b/src/uu/mkdir/src/mkdir.rs @@ -38,31 +38,27 @@ fn get_mode(_matches: &ArgMatches, _mode_had_minus_prefix: bool) -> Result Result { - let digits: &[char] = &['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']; - // Translate a ~str in octal form to u16, default to 777 // Not tested on Windows let mut new_mode = DEFAULT_PERM; - match matches.get_one::(options::MODE) { - Some(m) => { - for mode in m.split(',') { - if mode.contains(digits) { - new_mode = mode::parse_numeric(new_mode, m, true)?; + + if let Some(m) = matches.get_one::(options::MODE) { + for mode in m.split(',') { + if mode.chars().any(|c| c.is_ascii_digit()) { + new_mode = mode::parse_numeric(new_mode, m, true)?; + } else { + let cmode = if mode_had_minus_prefix { + // clap parsing is finished, now put prefix back + format!("-{mode}") } else { - let cmode = if mode_had_minus_prefix { - // clap parsing is finished, now put prefix back - format!("-{mode}") - } else { - mode.to_string() - }; - new_mode = mode::parse_symbolic(new_mode, &cmode, mode::get_umask(), true)?; - } + mode.to_string() + }; + new_mode = mode::parse_symbolic(new_mode, &cmode, mode::get_umask(), true)?; } - Ok(new_mode) - } - None => { - // If no mode argument is specified return the mode derived from umask - Ok(!mode::get_umask() & 0o0777) } + Ok(new_mode) + } else { + // If no mode argument is specified return the mode derived from umask + Ok(!mode::get_umask() & 0o0777) } } diff --git a/src/uu/mknod/src/parsemode.rs b/src/uu/mknod/src/parsemode.rs index adacaa45bcd..c38800bcb0d 100644 --- a/src/uu/mknod/src/parsemode.rs +++ b/src/uu/mknod/src/parsemode.rs @@ -11,8 +11,7 @@ use uucore::mode; pub const MODE_RW_UGO: mode_t = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; pub fn parse_mode(mode: &str) -> Result { - let arr: &[char] = &['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']; - let result = if mode.contains(arr) { + let result = if mode.chars().any(|c| c.is_ascii_digit()) { mode::parse_numeric(MODE_RW_UGO as u32, mode) } else { mode::parse_symbolic(MODE_RW_UGO as u32, mode, true) diff --git a/src/uucore/src/lib/features/mode.rs b/src/uucore/src/lib/features/mode.rs index cbaea71bf01..147624891bd 100644 --- a/src/uucore/src/lib/features/mode.rs +++ b/src/uucore/src/lib/features/mode.rs @@ -147,8 +147,7 @@ pub fn parse_mode(mode: &str) -> Result { #[cfg(any(target_os = "freebsd", target_vendor = "apple", target_os = "android"))] let fperm = (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH) as u32; - let arr: &[char] = &['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']; - let result = if mode.contains(arr) { + let result = if mode.chars().any(|c| c.is_ascii_digit()) { parse_numeric(fperm, mode, true) } else { parse_symbolic(fperm, mode, get_umask(), true) From 718a527e9be7dbaf15b7369f2b7650e9729a9e93 Mon Sep 17 00:00:00 2001 From: tommady Date: Sat, 30 Sep 2023 07:09:57 +0000 Subject: [PATCH 0145/2851] add testcase for no preserve mode --- tests/by-util/test_cp.rs | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/tests/by-util/test_cp.rs b/tests/by-util/test_cp.rs index 1ce74572d5d..f6d339a7ebc 100644 --- a/tests/by-util/test_cp.rs +++ b/tests/by-util/test_cp.rs @@ -1550,6 +1550,32 @@ fn test_cp_preserve_links_case_7() { assert!(at.plus("dest").join("g").exists()); } +#[test] +#[cfg(all(unix, not(target_os = "freebsd")))] +fn test_cp_no_preserve_mode_case() { + use libc::umask; + use uucore::fs as uufs; + let (at, mut ucmd) = at_and_ucmd!(); + + at.touch("a"); + at.set_mode("a", 0o731); + unsafe { umask(0o077) }; + + ucmd.arg("-a") + .arg("--no-preserve=mode") + .arg("a") + .arg("b") + .succeeds(); + + assert!(at.file_exists("b")); + + let metadata_b = std::fs::metadata(at.subdir.join("b")).unwrap(); + let permission_b = uufs::display_permissions(&metadata_b, false); + assert_eq!(permission_b, "rw-------".to_string()); + + unsafe { umask(0o022) }; +} + #[test] // For now, disable the test on Windows. Symlinks aren't well support on Windows. // It works on Unix for now and it works locally when run from a powershell From f92066cee2554813aacd5dceac19c5d79b571b0f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 30 Sep 2023 13:25:14 +0000 Subject: [PATCH 0146/2851] chore(deps): update rust crate regex to 1.9.6 --- Cargo.lock | 8 ++++---- Cargo.toml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c007551987f..4dc36c88f99 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1740,9 +1740,9 @@ checksum = "f1bfbf25d7eb88ddcbb1ec3d755d0634da8f7657b2cb8b74089121409ab8228f" [[package]] name = "regex" -version = "1.9.5" +version = "1.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "697061221ea1b4a94a624f67d0ae2bfe4e22b8a17b6a192afb11046542cc8c47" +checksum = "ebee201405406dbf528b8b672104ae6d6d63e6d118cb10e4d51abbc7b58044ff" dependencies = [ "aho-corasick", "memchr", @@ -1752,9 +1752,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2f401f4955220693b56f8ec66ee9c78abffd8d1c4f23dc41a23839eb88f0795" +checksum = "59b23e92ee4318893fa3fe3e6fb365258efbfe6ac6ab30f090cdcbb7aa37efa9" dependencies = [ "aho-corasick", "memchr", diff --git a/Cargo.toml b/Cargo.toml index a40d066d4ef..831d4ae4851 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -310,7 +310,7 @@ rand = { version = "0.8", features = ["small_rng"] } rand_core = "0.6" rayon = "1.8" redox_syscall = "0.4" -regex = "1.9.5" +regex = "1.9.6" rstest = "0.18.2" rust-ini = "0.19.0" same-file = "1.0.6" From 95ccc54d05892db5630e1a9413474041605fe2f0 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Sat, 30 Sep 2023 15:35:05 +0200 Subject: [PATCH 0147/2851] nl: defer showing "line number overflow" error --- src/uu/nl/src/nl.rs | 17 ++++++++++------- tests/by-util/test_nl.rs | 16 ++++++++++++++++ 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/src/uu/nl/src/nl.rs b/src/uu/nl/src/nl.rs index ea37e00dce1..71b4aac2886 100644 --- a/src/uu/nl/src/nl.rs +++ b/src/uu/nl/src/nl.rs @@ -56,14 +56,14 @@ impl Default for Settings { } struct Stats { - line_number: i64, + line_number: Option, consecutive_empty_lines: u64, } impl Stats { fn new(starting_line_number: i64) -> Self { Self { - line_number: starting_line_number, + line_number: Some(starting_line_number), consecutive_empty_lines: 0, } } @@ -344,7 +344,7 @@ fn nl(reader: &mut BufReader, stats: &mut Stats, settings: &Settings if let Some(new_style) = new_numbering_style { current_numbering_style = new_style; if settings.renumber { - stats.line_number = settings.starting_line_number; + stats.line_number = Some(settings.starting_line_number); } println!(); } else { @@ -364,18 +364,21 @@ fn nl(reader: &mut BufReader, stats: &mut Stats, settings: &Settings }; if is_line_numbered { + let Some(line_number) = stats.line_number else { + return Err(USimpleError::new(1, "line number overflow")); + }; println!( "{}{}{}", settings .number_format - .format(stats.line_number, settings.number_width), + .format(line_number, settings.number_width), settings.number_separator, line ); // update line number for the potential next line - match stats.line_number.checked_add(settings.line_increment) { - Some(new_line_number) => stats.line_number = new_line_number, - None => return Err(USimpleError::new(1, "line number overflow")), + match line_number.checked_add(settings.line_increment) { + Some(new_line_number) => stats.line_number = Some(new_line_number), + None => stats.line_number = None, // overflow } } else { let spaces = " ".repeat(settings.number_width + 1); diff --git a/tests/by-util/test_nl.rs b/tests/by-util/test_nl.rs index 87f218166e2..78c8975a849 100644 --- a/tests/by-util/test_nl.rs +++ b/tests/by-util/test_nl.rs @@ -539,6 +539,22 @@ fn test_line_number_overflow() { .stderr_is("nl: line number overflow\n"); } +#[test] +fn test_line_number_no_overflow() { + new_ucmd!() + .arg(format!("--starting-line-number={}", i64::MAX)) + .pipe_in("a\n\\:\\:\nb") + .succeeds() + .stdout_is(format!("{0}\ta\n\n{0}\tb\n", i64::MAX)); + + new_ucmd!() + .arg(format!("--starting-line-number={}", i64::MIN)) + .arg("--line-increment=-1") + .pipe_in("a\n\\:\\:\nb") + .succeeds() + .stdout_is(format!("{0}\ta\n\n{0}\tb\n", i64::MIN)); +} + #[test] fn test_section_delimiter() { for arg in ["-dabc", "--section-delimiter=abc"] { From 7337cd51691b0345308c1e1c37f2a7d1e1b3390e Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 1 Oct 2023 09:58:09 +0200 Subject: [PATCH 0148/2851] ls -R1: add a test to replicate GNU's recursive.sh --- tests/by-util/test_ls.rs | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/tests/by-util/test_ls.rs b/tests/by-util/test_ls.rs index 23dfafa3200..7d0f86298cd 100644 --- a/tests/by-util/test_ls.rs +++ b/tests/by-util/test_ls.rs @@ -1928,6 +1928,35 @@ fn test_ls_recursive() { result.stdout_contains("a\\b:\nb"); } +#[test] +fn test_ls_recursive_1() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + at.mkdir("x"); + at.mkdir("y"); + at.mkdir("a"); + at.mkdir("b"); + at.mkdir("c"); + at.mkdir("a/1"); + at.mkdir("a/2"); + at.mkdir("a/3"); + at.touch("f"); + at.touch("a/1/I"); + at.touch("a/1/II"); + #[cfg(unix)] + let out = "a:\n1\n2\n3\n\na/1:\nI\nII\n\na/2:\n\na/3:\n\nb:\n\nc:\n"; + #[cfg(windows)] + let out = "a:\n1\n2\n3\n\na\\1:\nI\nII\n\na\\2:\n\na\\3:\n\nb:\n\nc:\n"; + scene + .ucmd() + .arg("-R1") + .arg("a") + .arg("b") + .arg("c") + .succeeds() + .stdout_is(out); +} + #[test] fn test_ls_color() { let scene = TestScenario::new(util_name!()); From c27fcc4084cf0b3de255b16a34071e82e96d67c6 Mon Sep 17 00:00:00 2001 From: Daniel Clarke Date: Sun, 1 Oct 2023 12:01:46 -0400 Subject: [PATCH 0149/2851] Update parse_datetime to 0.5.0 (#5313) --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- src/uu/date/src/date.rs | 4 +++- src/uu/touch/src/touch.rs | 41 +++++++++++++------------------------ tests/by-util/test_touch.rs | 12 +++++++++++ 5 files changed, 32 insertions(+), 31 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4dc36c88f99..98f95032f8f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1499,9 +1499,9 @@ dependencies = [ [[package]] name = "parse_datetime" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fecceaede7767a9a98058687a321bc91742eff7670167a34104afb30fc8757df" +checksum = "3bbf4e25b13841080e018a1e666358adfe5e39b6d353f986ca5091c210b586a1" dependencies = [ "chrono", "regex", diff --git a/Cargo.toml b/Cargo.toml index 831d4ae4851..d6feeb0caa7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -301,7 +301,7 @@ num-traits = "0.2.16" number_prefix = "0.4" once_cell = "1.18.0" onig = { version = "~6.4", default-features = false } -parse_datetime = "0.4.0" +parse_datetime = "0.5.0" phf = "0.11.2" phf_codegen = "0.11.2" platform-info = "2.0.2" diff --git a/src/uu/date/src/date.rs b/src/uu/date/src/date.rs index 745fd54239c..b5ab8993acd 100644 --- a/src/uu/date/src/date.rs +++ b/src/uu/date/src/date.rs @@ -166,7 +166,9 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { }; let date_source = if let Some(date) = matches.get_one::(OPT_DATE) { - if let Ok(duration) = parse_datetime::from_str(date.as_str()) { + let ref_time = Local::now(); + if let Ok(new_time) = parse_datetime::parse_datetime_at_date(ref_time, date.as_str()) { + let duration = new_time.signed_duration_since(ref_time); DateSource::Human(duration) } else { DateSource::Custom(date.into()) diff --git a/src/uu/touch/src/touch.rs b/src/uu/touch/src/touch.rs index 85eb97bc462..6555773eedd 100644 --- a/src/uu/touch/src/touch.rs +++ b/src/uu/touch/src/touch.rs @@ -68,6 +68,10 @@ fn datetime_to_filetime(dt: &DateTime) -> FileTime { FileTime::from_unix_time(dt.timestamp(), dt.timestamp_subsec_nanos()) } +fn filetime_to_datetime(ft: &FileTime) -> Option> { + Some(DateTime::from_timestamp(ft.unix_seconds(), ft.nanoseconds())?.into()) +} + #[uucore::main] #[allow(clippy::cognitive_complexity)] pub fn uumain(args: impl uucore::Args) -> UResult<()> { @@ -88,35 +92,19 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { ) { (Some(reference), Some(date)) => { let (atime, mtime) = stat(Path::new(reference), !matches.get_flag(options::NO_DEREF))?; - if let Ok(offset) = parse_datetime::from_str(date) { - let seconds = offset.num_seconds(); - let nanos = offset.num_nanoseconds().unwrap_or(0) % 1_000_000_000; - - let ref_atime_secs = atime.unix_seconds(); - let ref_atime_nanos = atime.nanoseconds(); - let atime = FileTime::from_unix_time( - ref_atime_secs + seconds, - ref_atime_nanos + nanos as u32, - ); - - let ref_mtime_secs = mtime.unix_seconds(); - let ref_mtime_nanos = mtime.nanoseconds(); - let mtime = FileTime::from_unix_time( - ref_mtime_secs + seconds, - ref_mtime_nanos + nanos as u32, - ); - - (atime, mtime) - } else { - let timestamp = parse_date(date)?; - (timestamp, timestamp) - } + let atime = filetime_to_datetime(&atime).ok_or_else(|| { + USimpleError::new(1, "Could not process the reference access time") + })?; + let mtime = filetime_to_datetime(&mtime).ok_or_else(|| { + USimpleError::new(1, "Could not process the reference modification time") + })?; + (parse_date(atime, date)?, parse_date(mtime, date)?) } (Some(reference), None) => { stat(Path::new(reference), !matches.get_flag(options::NO_DEREF))? } (None, Some(date)) => { - let timestamp = parse_date(date)?; + let timestamp = parse_date(Local::now(), date)?; (timestamp, timestamp) } (None, None) => { @@ -336,7 +324,7 @@ fn stat(path: &Path, follow: bool) -> UResult<(FileTime, FileTime)> { )) } -fn parse_date(s: &str) -> UResult { +fn parse_date(ref_time: DateTime, s: &str) -> UResult { // This isn't actually compatible with GNU touch, but there doesn't seem to // be any simple specification for what format this parameter allows and I'm // not about to implement GNU parse_datetime. @@ -385,8 +373,7 @@ fn parse_date(s: &str) -> UResult { } } - if let Ok(duration) = parse_datetime::from_str(s) { - let dt = Local::now() + duration; + if let Ok(dt) = parse_datetime::parse_datetime_at_date(ref_time, s) { return Ok(datetime_to_filetime(&dt)); } diff --git a/tests/by-util/test_touch.rs b/tests/by-util/test_touch.rs index c9c0d700e2a..7b659fc5155 100644 --- a/tests/by-util/test_touch.rs +++ b/tests/by-util/test_touch.rs @@ -844,3 +844,15 @@ fn test_touch_dash() { ucmd.args(&["-h", "-"]).succeeds().no_stderr().no_stdout(); } + +#[test] +// Chrono panics for now +#[ignore] +fn test_touch_invalid_date_format() { + let (_at, mut ucmd) = at_and_ucmd!(); + let file = "test_touch_invalid_date_format"; + + ucmd.args(&["-m", "-t", "+1000000000000 years", file]) + .fails() + .stderr_contains("touch: invalid date format ‘+1000000000000 years’"); +} From d4220e9bb75a524707474dfca4f3c04f71f1d275 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 1 Oct 2023 20:03:02 +0000 Subject: [PATCH 0150/2851] chore(deps): update rust crate bytecount to 0.6.4 --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 98f95032f8f..76e1dd72c31 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -205,9 +205,9 @@ checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" [[package]] name = "bytecount" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c676a478f63e9fa2dd5368a42f28bba0d6c560b775f38583c8bbaa7fcd67c9c" +checksum = "ad152d03a2c813c80bb94fedbf3a3f02b28f793e39e7c214c8a0bcc196343de7" [[package]] name = "byteorder" diff --git a/Cargo.toml b/Cargo.toml index d6feeb0caa7..c4c3fc2ff0a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -261,7 +261,7 @@ test = ["uu_test"] bigdecimal = "0.4" binary-heap-plus = "0.5.0" bstr = "1.6" -bytecount = "0.6.3" +bytecount = "0.6.4" byteorder = "1.4.3" chrono = { version = "^0.4.31", default-features = false, features = [ "std", From fda762b91cfe1833830583d03668dda6ff0c0e28 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Mon, 2 Oct 2023 14:31:41 +0200 Subject: [PATCH 0151/2851] mv: show no "skipped" msg with -vi/-vin --- src/uu/mv/src/mv.rs | 10 +--------- tests/by-util/test_mv.rs | 5 +++-- 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/src/uu/mv/src/mv.rs b/src/uu/mv/src/mv.rs index 9f7a9661885..43f8eb6b651 100644 --- a/src/uu/mv/src/mv.rs +++ b/src/uu/mv/src/mv.rs @@ -448,19 +448,11 @@ fn rename( match b.overwrite { OverwriteMode::NoClobber => { - let err_msg = if b.verbose { - println!("skipped {}", to.quote()); - String::new() - } else { - format!("not replacing {}", to.quote()) - }; + let err_msg = format!("not replacing {}", to.quote()); return Err(io::Error::new(io::ErrorKind::Other, err_msg)); } OverwriteMode::Interactive => { if !prompt_yes!("overwrite {}?", to.quote()) { - if b.verbose { - println!("skipped {}", to.quote()); - } return Err(io::Error::new(io::ErrorKind::Other, "")); } } diff --git a/tests/by-util/test_mv.rs b/tests/by-util/test_mv.rs index eb8a30ac4e5..0490d41193f 100644 --- a/tests/by-util/test_mv.rs +++ b/tests/by-util/test_mv.rs @@ -1350,7 +1350,7 @@ fn test_mv_arg_interactive_skipped() { .ignore_stdin_write_error() .fails() .stderr_is("mv: overwrite 'b'? ") - .stdout_is("skipped 'b'\n"); + .no_stdout(); } #[test] @@ -1360,7 +1360,8 @@ fn test_mv_arg_interactive_skipped_vin() { at.touch("b"); ucmd.args(&["-vin", "a", "b"]) .fails() - .stdout_is("skipped 'b'\n"); + .stderr_is("mv: not replacing 'b'\n") + .no_stdout(); } #[test] From 8ee69d2b92b7cffa54e7118c989b0044d854ce60 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Mon, 2 Oct 2023 15:29:13 +0200 Subject: [PATCH 0152/2851] cp: show no "skipped" msg with -vi/-vin --- src/uu/cp/src/cp.rs | 19 ++++++------------- tests/by-util/test_cp.rs | 6 ++++-- 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index b6270719cc9..f9f6d87634a 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -1276,23 +1276,16 @@ fn copy_source( } impl OverwriteMode { - fn verify(&self, path: &Path, verbose: bool) -> CopyResult<()> { + fn verify(&self, path: &Path) -> CopyResult<()> { match *self { Self::NoClobber => { - if verbose { - println!("skipped {}", path.quote()); - } else { - eprintln!("{}: not replacing {}", util_name(), path.quote()); - } + eprintln!("{}: not replacing {}", util_name(), path.quote()); Err(Error::NotAllFilesCopied) } Self::Interactive(_) => { if prompt_yes!("overwrite {}?", path.quote()) { Ok(()) } else { - if verbose { - println!("skipped {}", path.quote()); - } Err(Error::Skipped) } } @@ -1500,7 +1493,7 @@ fn handle_existing_dest( return Err(format!("{} and {} are the same file", source.quote(), dest.quote()).into()); } - options.overwrite.verify(dest, options.verbose)?; + options.overwrite.verify(dest)?; let backup_path = backup_control::get_backup_path(options.backup, dest, &options.backup_suffix); if let Some(backup_path) = backup_path { @@ -1895,7 +1888,7 @@ fn copy_helper( File::create(dest).context(dest.display().to_string())?; } else if source_is_fifo && options.recursive && !options.copy_contents { #[cfg(unix)] - copy_fifo(dest, options.overwrite, options.verbose)?; + copy_fifo(dest, options.overwrite)?; } else if source_is_symlink { copy_link(source, dest, symlinked_files)?; } else { @@ -1920,9 +1913,9 @@ fn copy_helper( // "Copies" a FIFO by creating a new one. This workaround is because Rust's // built-in fs::copy does not handle FIFOs (see rust-lang/rust/issues/79390). #[cfg(unix)] -fn copy_fifo(dest: &Path, overwrite: OverwriteMode, verbose: bool) -> CopyResult<()> { +fn copy_fifo(dest: &Path, overwrite: OverwriteMode) -> CopyResult<()> { if dest.exists() { - overwrite.verify(dest, verbose)?; + overwrite.verify(dest)?; fs::remove_file(dest)?; } diff --git a/tests/by-util/test_cp.rs b/tests/by-util/test_cp.rs index 1ce74572d5d..70d4038bd9e 100644 --- a/tests/by-util/test_cp.rs +++ b/tests/by-util/test_cp.rs @@ -483,7 +483,8 @@ fn test_cp_arg_interactive_verbose() { ucmd.args(&["-vi", "a", "b"]) .pipe_in("N\n") .fails() - .stdout_is("skipped 'b'\n"); + .stderr_is("cp: overwrite 'b'? ") + .no_stdout(); } #[test] @@ -494,7 +495,8 @@ fn test_cp_arg_interactive_verbose_clobber() { at.touch("b"); ucmd.args(&["-vin", "a", "b"]) .fails() - .stdout_is("skipped 'b'\n"); + .stderr_is("cp: not replacing 'b'\n") + .no_stdout(); } #[test] From 9f6a720582f8a373cce9521cfe54610dab06f89c Mon Sep 17 00:00:00 2001 From: Yury Zhytkou <54360928+zhitkoff@users.noreply.github.com> Date: Mon, 2 Oct 2023 15:46:00 -0400 Subject: [PATCH 0153/2851] Introducing DEVELOPMENT.md (#5209) --- CONTRIBUTING.md | 225 +-------------------------------- DEVELOPMENT.md | 329 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 335 insertions(+), 219 deletions(-) create mode 100644 DEVELOPMENT.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6f67eb828db..695e5ad18ef 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -38,205 +38,15 @@ CI. However, you can use `#[cfg(...)]` attributes to create platform dependent f VirtualBox and Parallels) for development: -## Tools +## Setting up your development environment -We have an extensive CI that will check your code before it can be merged. This -section explains how to run those checks locally to avoid waiting for the CI. +To setup your local development environment for this project please follow [DEVELOPMENT.md guide](DEVELOPMENT.md) -### pre-commit hooks +It covers [installation of necessary tools and prerequisites](DEVELOPMENT.md#tools) as well as using those tools to [test your code changes locally](DEVELOPMENT.md#testing) -A configuration for `pre-commit` is provided in the repository. It allows -automatically checking every git commit you make to ensure it compiles, and -passes `clippy` and `rustfmt` without warnings. +## Improving the GNU compatibility -To use the provided hook: - -1. [Install `pre-commit`](https://pre-commit.com/#install) -1. Run `pre-commit install` while in the repository directory - -Your git commits will then automatically be checked. If a check fails, an error -message will explain why, and your commit will be canceled. You can then make -the suggested changes, and run `git commit ...` again. - -### clippy - -```shell -cargo clippy --all-targets --all-features -``` - -The `msrv` key in the clippy configuration file `clippy.toml` is used to disable -lints pertaining to newer features by specifying the minimum supported Rust -version (MSRV). - -### rustfmt - -```shell -cargo fmt --all -``` - -### cargo-deny - -This project uses [cargo-deny](https://github.com/EmbarkStudios/cargo-deny/) to -detect duplicate dependencies, checks licenses, etc. To run it locally, first -install it and then run with: - -``` -cargo deny --all-features check all -``` - -### Markdown linter - -We use [markdownlint](https://github.com/DavidAnson/markdownlint) to lint the -Markdown files in the repository. - -### Spell checker - -We use `cspell` as spell checker for all files in the project. If you are using -VS Code, you can install the -[code spell checker](https://marketplace.visualstudio.com/items?itemName=streetsidesoftware.code-spell-checker) -extension to enable spell checking within your editor. Otherwise, you can -install [cspell](https://cspell.org/) separately. - -If you want to make the spell checker ignore a word, you can add - -```rust -// spell-checker:ignore word_to_ignore -``` - -at the top of the file. - -## Testing - -Testing can be done using either Cargo or `make`. - -### Testing with Cargo - -Just like with building, we follow the standard procedure for testing using -Cargo: - -```shell -cargo test -``` - -By default, `cargo test` only runs the common programs. To run also platform -specific tests, run: - -```shell -cargo test --features unix -``` - -If you would prefer to test a select few utilities: - -```shell -cargo test --features "chmod mv tail" --no-default-features -``` - -If you also want to test the core utilities: - -```shell -cargo test -p uucore -p coreutils -``` - -Or to test the pure Rust tests in the utility itself: - -```shell -cargo test -p uu_ls --lib -``` - -Running the complete test suite might take a while. We use [nextest](https://nexte.st/index.html) in -the CI and you might want to try it out locally. It can speed up the execution time of the whole -test run significantly if the cpu has multiple cores. - -```shell -cargo nextest run --features unix --no-fail-fast -``` - -To debug: - -```shell -gdb --args target/debug/coreutils ls -(gdb) b ls.rs:79 -(gdb) run -``` - -### Testing with GNU Make - -To simply test all available utilities: - -```shell -make test -``` - -To test all but a few of the available utilities: - -```shell -make SKIP_UTILS='UTILITY_1 UTILITY_2' test -``` - -To test only a few of the available utilities: - -```shell -make UTILS='UTILITY_1 UTILITY_2' test -``` - -To include tests for unimplemented behavior: - -```shell -make UTILS='UTILITY_1 UTILITY_2' SPEC=y test -``` - -To run tests with `nextest` just use the nextest target. Note you'll need to -[install](https://nexte.st/book/installation.html) `nextest` first. The `nextest` target accepts the -same arguments like the default `test` target, so it's possible to pass arguments to `nextest run` -via `CARGOFLAGS`: - -```shell -make CARGOFLAGS='--no-fail-fast' UTILS='UTILITY_1 UTILITY_2' nextest -``` - -### Run Busybox Tests - -This testing functionality is only available on *nix operating systems and -requires `make`. - -To run busybox tests for all utilities for which busybox has tests - -```shell -make busytest -``` - -To run busybox tests for a few of the available utilities - -```shell -make UTILS='UTILITY_1 UTILITY_2' busytest -``` - -To pass an argument like "-v" to the busybox test runtime - -```shell -make UTILS='UTILITY_1 UTILITY_2' RUNTEST_ARGS='-v' busytest -``` - -### Comparing with GNU - -To run uutils against the GNU test suite locally, run the following commands: - -```shell -bash util/build-gnu.sh -# Build uutils without release optimizations -UU_MAKE_PROFILE=debug bash util/build-gnu.sh -bash util/run-gnu-test.sh -# To run a single test: -bash util/run-gnu-test.sh tests/touch/not-owner.sh # for example -# To run several tests: -bash util/run-gnu-test.sh tests/touch/not-owner.sh tests/rm/no-give-up.sh # for example -# If this is a perl (.pl) test, to run in debug: -DEBUG=1 bash util/run-gnu-test.sh tests/misc/sm3sum.pl -``` - -Note that it relies on individual utilities (not the multicall binary). - -### Improving the GNU compatibility +Please make sure you have installed [GNU utils and prerequisites](DEVELOPMENT.md#gnu-utils-and-prerequisites) and can execute commands described in [Comparing with GNU](DEVELOPMENT.md#comparing-with-gnu) section of [DEVELOPMENT.md](DEVELOPMENT.md) The Python script `./util/remaining-gnu-error.py` shows the list of failing tests in the CI. @@ -326,30 +136,7 @@ gitignore: add temporary files ## Code coverage - - -Code coverage report can be generated using [grcov](https://github.com/mozilla/grcov). - -### Using Nightly Rust - -To generate [gcov-based](https://github.com/mozilla/grcov#example-how-to-generate-gcda-files-for-a-rust-project) coverage report - -```shell -export CARGO_INCREMENTAL=0 -export RUSTFLAGS="-Zprofile -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort" -export RUSTDOCFLAGS="-Cpanic=abort" -cargo build # e.g., --features feat_os_unix -cargo test # e.g., --features feat_os_unix test_pathchk -grcov . -s . --binary-path ./target/debug/ -t html --branch --ignore-not-existing --ignore build.rs --excl-br-line "^\s*((debug_)?assert(_eq|_ne)?\#\[derive\()" -o ./target/debug/coverage/ -# open target/debug/coverage/index.html in browser -``` - -if changes are not reflected in the report then run `cargo clean` and run the above commands. - -### Using Stable Rust - -If you are using stable version of Rust that doesn't enable code coverage instrumentation by default -then add `-Z-Zinstrument-coverage` flag to `RUSTFLAGS` env variable specified above. +To generate code coverage report locally please follow [Code coverage report](DEVELOPMENT.md#code-coverage-report) section of [DEVELOPMENT.md](DEVELOPMENT.md) ## Other implementations diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md new file mode 100644 index 00000000000..24a1bdeb52c --- /dev/null +++ b/DEVELOPMENT.md @@ -0,0 +1,329 @@ + + +# Setting up your local development environment + +For contributing rules and best practices please refer to [CONTRIBUTING.md](CONTRIBUTING.md) + +## Before you start + +For this guide we assume that you already have GitHub account and have `git` and your favorite code editor or IDE installed and configured. +Before you start working on coreutils, please follow these steps: + +1. Fork the [coreutils repository](https://github.com/uutils/coreutils) to your GitHub account. +***Tip:*** See [this GitHub guide](https://docs.github.com/en/get-started/quickstart/fork-a-repo) for more information on this step. +2. Clone that fork to your local development environment: + +```shell +git clone https://github.com/YOUR-GITHUB-ACCOUNT/coreutils +cd coreutils +``` + +## Tools + +You will need the tools mentioned in this section to build and test your code changes locally. +This section will explain how to install and configure these tools. +We also have an extensive CI that uses these tools and will check your code before it can be merged. +The next section [Testing](#testing) will explain how to run those checks locally to avoid waiting for the CI. + +### Rust toolchain + +[Install Rust](https://www.rust-lang.org/tools/install) + +If you're using rustup to install and manage your Rust toolchains, `clippy` and `rustfmt` are usually already installed. If you are using one of the alternative methods, please make sure to install them manually. See following sub-sections for their usage: [clippy](#clippy) [rustfmt](#rustfmt). + +***Tip*** You might also need to add 'llvm-tools' component if you are going to [generate code coverage reports locally](#code-coverage-report): + +```shell +rustup component add llvm-tools-preview +``` + +### GNU utils and prerequisites + +If you are developing on Linux, most likely you already have all/most GNU utilities and prerequisites installed. + +To make sure, please check GNU coreutils [README-prereq](https://github.com/coreutils/coreutils/blob/master/README-prereq). + +You will need these to [run uutils against the GNU test suite locally](#comparing-with-gnu). + +For MacOS and Windows platform specific setup please check [MacOS GNU utils](#macos-gnu-utils) and [Windows GNU utils](#windows-gnu-utils) sections respectfully. + +### pre-commit hooks + +A configuration for `pre-commit` is provided in the repository. It allows +automatically checking every git commit you make to ensure it compiles, and +passes `clippy` and `rustfmt` without warnings. + +To use the provided hook: + +1. [Install `pre-commit`](https://pre-commit.com/#install) +1. Run `pre-commit install` while in the repository directory + +Your git commits will then automatically be checked. If a check fails, an error +message will explain why, and your commit will be canceled. You can then make +the suggested changes, and run `git commit ...` again. + +**NOTE: On MacOS** the pre-commit hooks are currently broken. There are workarounds involving switching to unstable nightly Rust and components. + +### clippy + +```shell +cargo clippy --all-targets --all-features +``` + +The `msrv` key in the clippy configuration file `clippy.toml` is used to disable +lints pertaining to newer features by specifying the minimum supported Rust +version (MSRV). + +### rustfmt + +```shell +cargo fmt --all +``` + +### cargo-deny + +This project uses [cargo-deny](https://github.com/EmbarkStudios/cargo-deny/) to +detect duplicate dependencies, checks licenses, etc. To run it locally, first +install it and then run with: + +```shell +cargo deny --all-features check all +``` + +### Markdown linter + +We use [markdownlint](https://github.com/DavidAnson/markdownlint) to lint the +Markdown files in the repository. + +### Spell checker + +We use `cspell` as spell checker for all files in the project. If you are using +VS Code, you can install the +[code spell checker](https://marketplace.visualstudio.com/items?itemName=streetsidesoftware.code-spell-checker) +extension to enable spell checking within your editor. Otherwise, you can +install [cspell](https://cspell.org/) separately. + +If you want to make the spell checker ignore a word, you can add + +```rust +// spell-checker:ignore word_to_ignore +``` + +at the top of the file. + +## Testing + +This section explains how to run our CI checks locally. +Testing can be done using either Cargo or `make`. + +### Testing with Cargo + +Just like with building, we follow the standard procedure for testing using +Cargo: + +```shell +cargo test +``` + +By default, `cargo test` only runs the common programs. To run also platform +specific tests, run: + +```shell +cargo test --features unix +``` + +If you would prefer to test a select few utilities: + +```shell +cargo test --features "chmod mv tail" --no-default-features +``` + +If you also want to test the core utilities: + +```shell +cargo test -p uucore -p coreutils +``` + +Running the complete test suite might take a while. We use [nextest](https://nexte.st/index.html) in +the CI and you might want to try it out locally. It can speed up the execution time of the whole +test run significantly if the cpu has multiple cores. + +```shell +cargo nextest run --features unix --no-fail-fast +``` + +To debug: + +```shell +gdb --args target/debug/coreutils ls +(gdb) b ls.rs:79 +(gdb) run +``` + +### Testing with GNU Make + +To simply test all available utilities: + +```shell +make test +``` + +To test all but a few of the available utilities: + +```shell +make SKIP_UTILS='UTILITY_1 UTILITY_2' test +``` + +To test only a few of the available utilities: + +```shell +make UTILS='UTILITY_1 UTILITY_2' test +``` + +To include tests for unimplemented behavior: + +```shell +make UTILS='UTILITY_1 UTILITY_2' SPEC=y test +``` + +To run tests with `nextest` just use the nextest target. Note you'll need to +[install](https://nexte.st/book/installation.html) `nextest` first. The `nextest` target accepts the +same arguments like the default `test` target, so it's possible to pass arguments to `nextest run` +via `CARGOFLAGS`: + +```shell +make CARGOFLAGS='--no-fail-fast' UTILS='UTILITY_1 UTILITY_2' nextest +``` + +### Run Busybox Tests + +This testing functionality is only available on *nix operating systems and +requires `make`. + +To run busybox tests for all utilities for which busybox has tests + +```shell +make busytest +``` + +To run busybox tests for a few of the available utilities + +```shell +make UTILS='UTILITY_1 UTILITY_2' busytest +``` + +To pass an argument like "-v" to the busybox test runtime + +```shell +make UTILS='UTILITY_1 UTILITY_2' RUNTEST_ARGS='-v' busytest +``` + +### Comparing with GNU + +To run uutils against the GNU test suite locally, run the following commands: + +```shell +bash util/build-gnu.sh +# Build uutils without release optimizations +UU_MAKE_PROFILE=debug bash util/build-gnu.sh +bash util/run-gnu-test.sh +# To run a single test: +bash util/run-gnu-test.sh tests/touch/not-owner.sh # for example +# To run several tests: +bash util/run-gnu-test.sh tests/touch/not-owner.sh tests/rm/no-give-up.sh # for example +# If this is a perl (.pl) test, to run in debug: +DEBUG=1 bash util/run-gnu-test.sh tests/misc/sm3sum.pl +``` + +***Tip:*** First time you run `bash util/build-gnu.sh` command, it will provide instructions on how to checkout GNU coreutils repository at the correct release tag. Please follow those instructions and when done, run `bash util/build-gnu.sh` command again. + +Note that GNU test suite relies on individual utilities (not the multicall binary). + +## Code coverage report + +Code coverage report can be generated using [grcov](https://github.com/mozilla/grcov). + +### Using Nightly Rust + +To generate [gcov-based](https://github.com/mozilla/grcov#example-how-to-generate-gcda-files-for-a-rust-project) coverage report + +```shell +export CARGO_INCREMENTAL=0 +export RUSTFLAGS="-Zprofile -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort" +export RUSTDOCFLAGS="-Cpanic=abort" +cargo build # e.g., --features feat_os_unix +cargo test # e.g., --features feat_os_unix test_pathchk +grcov . -s . --binary-path ./target/debug/ -t html --branch --ignore-not-existing --ignore build.rs --excl-br-line "^\s*((debug_)?assert(_eq|_ne)?\#\[derive\()" -o ./target/debug/coverage/ +# open target/debug/coverage/index.html in browser +``` + +if changes are not reflected in the report then run `cargo clean` and run the above commands. + +### Using Stable Rust + +If you are using stable version of Rust that doesn't enable code coverage instrumentation by default +then add `-Z-Zinstrument-coverage` flag to `RUSTFLAGS` env variable specified above. + +## Tips for setting up on Mac + +### C Compiler and linker + +On MacOS you'll need to install C compiler & linker: + +```shell +xcode-select --install +``` + +### MacOS GNU utils + +On MacOS you will need to install [Homebrew](https://docs.brew.sh/Installation) and use it to install the following Homebrew formulas: + +```shell +brew install \ + coreutils \ + autoconf \ + gettext \ + wget \ + texinfo \ + xz \ + automake \ + gnu-sed \ + m4 \ + bison \ + pre-commit \ + findutils +``` + +After installing these Homebrew formulas, please make sure to add the following lines to your `zsh` or `bash` rc file, i.e. `~/.profile` or `~/.zshrc` or `~/.bashrc` ... +(assuming Homebrew is installed at default location `/opt/homebrew`): + +```shell +eval "$(/opt/homebrew/bin/brew shellenv)" +export PATH="/opt/homebrew/opt/coreutils/libexec/gnubin:$PATH" +export PATH="/opt/homebrew/opt/bison/bin:$PATH" +export PATH="/opt/homebrew/opt/findutils/libexec/gnubin:$PATH" +``` + +Last step is to link Homebrew coreutils version of `timeout` to `/usr/local/bin` (as admin user): + +```shell +sudo ln -s /opt/homebrew/bin/timeout /usr/local/bin/timeout +``` + +Do not forget to either source updated rc file or restart you terminal session to update environment variables. + +## Tips for setting up on Windows + +### MSVC build tools + +On Windows you'll need the MSVC build tools for Visual Studio 2013 or later. + +If you are using `rustup-init.exe` to install Rust toolchain, it will guide you through the process of downloading and installing these prerequisites. + +Otherwise please follow [this guide](https://learn.microsoft.com/en-us/windows/dev-environment/rust/setup). + +### Windows GNU utils + +If you have used [Git for Windows](https://gitforwindows.org) to install `git` on you Windows system you might already have some GNU core utilities installed as part of "GNU Bash" included in Git for Windows package, but it is not a complete package. [This article](https://gist.github.com/evanwill/0207876c3243bbb6863e65ec5dc3f058) provides instruction on how to add more to it. + +Alternatively you can install [Cygwin](https://www.cygwin.com) and/or use [WSL2](https://learn.microsoft.com/en-us/windows/wsl/compare-versions#whats-new-in-wsl-2) to get access to all GNU core utilities on Windows. From c5a0aa92f8b31741e2be6128fbb9c5f29d22ec69 Mon Sep 17 00:00:00 2001 From: Yury Zhytkou <54360928+zhitkoff@users.noreply.github.com> Date: Mon, 2 Oct 2023 18:42:46 -0400 Subject: [PATCH 0154/2851] split: implementing separator option (#5331) * split: implementing separator option * split: separator option - handle multiple update * split: style * split: separator tests * split: separator tests - stdin in ci/cd * split: tests - ci/cd stdin errors * split: refactor based on feedback * split: improve test coverage * split: fix broken pipe error in tests with stdin * split: fix for handle_multiple_separator_options * split: comments * split: refactor separator code * split: changes based on feedback * split: changes based on feedback --- src/uu/split/split.md | 13 + src/uu/split/src/split.rs | 133 ++++++++--- tests/by-util/test_split.rs | 239 +++++++++++++++++++ tests/fixtures/split/separator_nul.txt | Bin 0 -> 10 bytes tests/fixtures/split/separator_semicolon.txt | 1 + 5 files changed, 348 insertions(+), 38 deletions(-) create mode 100644 tests/fixtures/split/separator_nul.txt create mode 100644 tests/fixtures/split/separator_semicolon.txt diff --git a/src/uu/split/split.md b/src/uu/split/split.md index d3a481fd307..836e3a0c69a 100644 --- a/src/uu/split/split.md +++ b/src/uu/split/split.md @@ -11,3 +11,16 @@ Create output files containing consecutive or interleaved sections of input ## After Help Output fixed-size pieces of INPUT to PREFIXaa, PREFIXab, ...; default size is 1000, and default PREFIX is 'x'. With no INPUT, or when INPUT is -, read standard input. + +The SIZE argument is an integer and optional unit (example: 10K is 10*1024). +Units are K,M,G,T,P,E,Z,Y,R,Q (powers of 1024) or KB,MB,... (powers of 1000). +Binary prefixes can be used, too: KiB=K, MiB=M, and so on. + +CHUNKS may be: + +- N split into N files based on size of input +- K/N output Kth of N to stdout +- l/N split into N files without splitting lines/records +- l/K/N output Kth of N to stdout without splitting lines/records +- r/N like 'l' but use round robin distribution +- r/K/N likewise but only output Kth of N to stdout diff --git a/src/uu/split/src/split.rs b/src/uu/split/src/split.rs index a61c0e812de..75624853953 100644 --- a/src/uu/split/src/split.rs +++ b/src/uu/split/src/split.rs @@ -11,7 +11,7 @@ mod platform; use crate::filenames::FilenameIterator; use crate::filenames::SuffixType; -use clap::{crate_version, parser::ValueSource, Arg, ArgAction, ArgMatches, Command}; +use clap::{crate_version, parser::ValueSource, Arg, ArgAction, ArgMatches, Command, ValueHint}; use std::env; use std::ffi::OsString; use std::fmt; @@ -39,6 +39,7 @@ static OPT_HEX_SUFFIXES_SHORT: &str = "-x"; static OPT_SUFFIX_LENGTH: &str = "suffix-length"; static OPT_DEFAULT_SUFFIX_LENGTH: &str = "0"; static OPT_VERBOSE: &str = "verbose"; +static OPT_SEPARATOR: &str = "separator"; //The ---io and ---io-blksize parameters are consumed and ignored. //The parameter is included to make GNU coreutils tests pass. static OPT_IO: &str = "-io"; @@ -55,7 +56,6 @@ const AFTER_HELP: &str = help_section!("after help", "split.md"); #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { let (args, obs_lines) = handle_obsolete(args); - let matches = uu_app().try_get_matches_from(args)?; match Settings::from(&matches, &obs_lines) { @@ -145,6 +145,7 @@ fn should_extract_obs_lines( && !slice.starts_with("-C") && !slice.starts_with("-l") && !slice.starts_with("-n") + && !slice.starts_with("-t") } /// Helper function to [`filter_args`] @@ -208,13 +209,18 @@ fn handle_preceding_options( || &slice[2..] == OPT_ADDITIONAL_SUFFIX || &slice[2..] == OPT_FILTER || &slice[2..] == OPT_NUMBER - || &slice[2..] == OPT_SUFFIX_LENGTH; + || &slice[2..] == OPT_SUFFIX_LENGTH + || &slice[2..] == OPT_SEPARATOR; } // capture if current slice is a preceding short option that requires value and does not have value in the same slice (value separated by whitespace) // following slice should be treaded as value for this option // even if it starts with '-' (which would be treated as hyphen prefixed value) - *preceding_short_opt_req_value = - slice == "-b" || slice == "-C" || slice == "-l" || slice == "-n" || slice == "-a"; + *preceding_short_opt_req_value = slice == "-b" + || slice == "-C" + || slice == "-l" + || slice == "-n" + || slice == "-a" + || slice == "-t"; // slice is a value // reset preceding option flags if !slice.starts_with('-') { @@ -278,7 +284,7 @@ pub fn uu_app() -> Command { .long(OPT_FILTER) .allow_hyphen_values(true) .value_name("COMMAND") - .value_hint(clap::ValueHint::CommandName) + .value_hint(ValueHint::CommandName) .help( "write to shell COMMAND; file name is $FILE (Currently not implemented for Windows)", ), @@ -293,7 +299,7 @@ pub fn uu_app() -> Command { .arg( Arg::new(OPT_NUMERIC_SUFFIXES_SHORT) .short('d') - .action(clap::ArgAction::SetTrue) + .action(ArgAction::SetTrue) .overrides_with_all([ OPT_NUMERIC_SUFFIXES, OPT_NUMERIC_SUFFIXES_SHORT, @@ -314,12 +320,13 @@ pub fn uu_app() -> Command { OPT_HEX_SUFFIXES, OPT_HEX_SUFFIXES_SHORT ]) + .value_name("FROM") .help("same as -d, but allow setting the start value"), ) .arg( Arg::new(OPT_HEX_SUFFIXES_SHORT) .short('x') - .action(clap::ArgAction::SetTrue) + .action(ArgAction::SetTrue) .overrides_with_all([ OPT_NUMERIC_SUFFIXES, OPT_NUMERIC_SUFFIXES_SHORT, @@ -340,6 +347,7 @@ pub fn uu_app() -> Command { OPT_HEX_SUFFIXES, OPT_HEX_SUFFIXES_SHORT ]) + .value_name("FROM") .help("same as -x, but allow setting the start value"), ) .arg( @@ -357,6 +365,15 @@ pub fn uu_app() -> Command { .help("print a diagnostic just before each output file is opened") .action(ArgAction::SetTrue), ) + .arg( + Arg::new(OPT_SEPARATOR) + .short('t') + .long(OPT_SEPARATOR) + .allow_hyphen_values(true) + .value_name("SEP") + .action(ArgAction::Append) + .help("use SEP instead of newline as the record separator; '\0' (zero) specifies the NUL character"), + ) .arg( Arg::new(OPT_IO) .long("io") @@ -372,7 +389,7 @@ pub fn uu_app() -> Command { .arg( Arg::new(ARG_INPUT) .default_value("-") - .value_hint(clap::ValueHint::FilePath), + .value_hint(ValueHint::FilePath), ) .arg( Arg::new(ARG_PREFIX) @@ -696,6 +713,7 @@ struct Settings { filter: Option, strategy: Strategy, verbose: bool, + separator: u8, /// Whether to *not* produce empty files when using `-n`. /// @@ -722,6 +740,12 @@ enum SettingsError { /// Suffix is not large enough to split into specified chunks SuffixTooSmall(usize), + /// Multi-character (Invalid) separator + MultiCharacterSeparator(String), + + /// Multiple different separator characters + MultipleSeparatorCharacters, + /// The `--filter` option is not supported on Windows. #[cfg(windows)] NotSupported, @@ -743,6 +767,12 @@ impl fmt::Display for SettingsError { Self::Strategy(e) => e.fmt(f), Self::SuffixNotParsable(s) => write!(f, "invalid suffix length: {}", s.quote()), Self::SuffixTooSmall(i) => write!(f, "the suffix length needs to be at least {i}"), + Self::MultiCharacterSeparator(s) => { + write!(f, "multi-character separator {}", s.quote()) + } + Self::MultipleSeparatorCharacters => { + write!(f, "multiple separator characters specified") + } Self::SuffixContainsSeparator(s) => write!( f, "invalid suffix {}, contains directory separator", @@ -783,6 +813,26 @@ impl Settings { } } } + + // Make sure that separator is only one UTF8 character (if specified) + // defaults to '\n' - newline character + // If the same separator (the same value) was used multiple times - `split` should NOT fail + // If the separator was used multiple times but with different values (not all values are the same) - `split` should fail + let separator = match matches.get_many::(OPT_SEPARATOR) { + Some(mut sep_values) => { + let first = sep_values.next().unwrap(); // it is safe to just unwrap here since Clap should not return empty ValuesRef<'_,String> in the option from get_many() call + if !sep_values.all(|s| s == first) { + return Err(SettingsError::MultipleSeparatorCharacters); + } + match first.as_str() { + "\\0" => b'\0', + s if s.as_bytes().len() == 1 => s.as_bytes()[0], + s => return Err(SettingsError::MultiCharacterSeparator(s.to_owned())), + } + } + None => b'\n', + }; + let result = Self { suffix_length: suffix_length_str .parse() @@ -791,6 +841,7 @@ impl Settings { suffix_start, additional_suffix, verbose: matches.value_source("verbose") == Some(ValueSource::CommandLine), + separator, strategy, input: matches.get_one::(ARG_INPUT).unwrap().to_owned(), prefix: matches.get_one::(ARG_PREFIX).unwrap().to_owned(), @@ -1019,7 +1070,8 @@ impl<'a> Write for LineChunkWriter<'a> { // corresponds to the current chunk number. let mut prev = 0; let mut total_bytes_written = 0; - for i in memchr::memchr_iter(b'\n', buf) { + let sep = self.settings.separator; + for i in memchr::memchr_iter(sep, buf) { // If we have exceeded the number of lines to write in the // current chunk, then start a new chunk and its // corresponding writer. @@ -1036,8 +1088,8 @@ impl<'a> Write for LineChunkWriter<'a> { } // Write the line, starting from *after* the previous - // newline character and ending *after* the current - // newline character. + // separator character and ending *after* the current + // separator character. let n = self.inner.write(&buf[prev..i + 1])?; total_bytes_written += n; prev = i + 1; @@ -1175,21 +1227,22 @@ impl<'a> Write for LineBytesChunkWriter<'a> { self.num_bytes_remaining_in_current_chunk = self.chunk_size.try_into().unwrap(); } - // Find the first newline character in the buffer. - match memchr::memchr(b'\n', buf) { - // If there is no newline character and the buffer is + // Find the first separator (default - newline character) in the buffer. + let sep = self.settings.separator; + match memchr::memchr(sep, buf) { + // If there is no separator character and the buffer is // not empty, then write as many bytes as we can and // then move on to the next chunk if necessary. None => { let end = self.num_bytes_remaining_in_current_chunk; // This is ugly but here to match GNU behavior. If the input - // doesn't end with a \n, pretend that it does for handling + // doesn't end with a separator, pretend that it does for handling // the second to last segment chunk. See `line-bytes.sh`. if end == buf.len() && self.num_bytes_remaining_in_current_chunk < self.chunk_size.try_into().unwrap() - && buf[buf.len() - 1] != b'\n' + && buf[buf.len() - 1] != sep { self.num_bytes_remaining_in_current_chunk = 0; } else { @@ -1200,8 +1253,8 @@ impl<'a> Write for LineBytesChunkWriter<'a> { } } - // If there is a newline character and the line - // (including the newline character) will fit in the + // If there is a separator character and the line + // (including the separator character) will fit in the // current chunk, then write the entire line and // continue to the next iteration. (See chunk 1 in the // example comment above.) @@ -1212,8 +1265,8 @@ impl<'a> Write for LineBytesChunkWriter<'a> { buf = &buf[num_bytes_written..]; } - // If there is a newline character, the line - // (including the newline character) will not fit in + // If there is a separator character, the line + // (including the separator character) will not fit in // the current chunk, *and* no other lines have been // written to the current chunk, then write as many // bytes as we can and continue to the next @@ -1230,8 +1283,8 @@ impl<'a> Write for LineBytesChunkWriter<'a> { buf = &buf[num_bytes_written..]; } - // If there is a newline character, the line - // (including the newline character) will not fit in + // If there is a separator character, the line + // (including the separator character) will not fit in // the current chunk, and at least one other line has // been written to the current chunk, then signal to // the next iteration that a new chunk needs to be @@ -1489,15 +1542,16 @@ where let mut num_bytes_remaining_in_current_chunk = chunk_size; let mut i = 0; - for line_result in reader.lines() { + let sep = settings.separator; + for line_result in reader.split(sep) { let line = line_result.unwrap(); let maybe_writer = writers.get_mut(i); let writer = maybe_writer.unwrap(); - let bytes = line.as_bytes(); + let bytes = line.as_slice(); writer.write_all(bytes)?; - writer.write_all(b"\n")?; + writer.write_all(&[sep])?; - // Add one byte for the newline character. + // Add one byte for the separator character. let num_bytes = bytes.len() + 1; if num_bytes > num_bytes_remaining_in_current_chunk { num_bytes_remaining_in_current_chunk = chunk_size; @@ -1546,15 +1600,16 @@ where let mut num_bytes_remaining_in_current_chunk = chunk_size; let mut i = 0; - for line_result in reader.lines() { + let sep = settings.separator; + for line_result in reader.split(sep) { let line = line_result?; - let bytes = line.as_bytes(); + let bytes = line.as_slice(); if i == chunk_number { writer.write_all(bytes)?; - writer.write_all(b"\n")?; + writer.write_all(&[sep])?; } - // Add one byte for the newline character. + // Add one byte for the separator character. let num_bytes = bytes.len() + 1; if num_bytes >= num_bytes_remaining_in_current_chunk { num_bytes_remaining_in_current_chunk = chunk_size; @@ -1601,13 +1656,14 @@ where } let num_chunks: usize = num_chunks.try_into().unwrap(); - for (i, line_result) in reader.lines().enumerate() { + let sep = settings.separator; + for (i, line_result) in reader.split(sep).enumerate() { let line = line_result.unwrap(); let maybe_writer = writers.get_mut(i % num_chunks); let writer = maybe_writer.unwrap(); - let bytes = line.as_bytes(); + let bytes = line.as_slice(); writer.write_all(bytes)?; - writer.write_all(b"\n")?; + writer.write_all(&[sep])?; } Ok(()) @@ -1632,7 +1688,7 @@ where /// * [`split_into_n_chunks_by_line_round_robin`], which splits its input in the /// same way, but writes each chunk to its own file. fn kth_chunk_by_line_round_robin( - _settings: &Settings, + settings: &Settings, reader: &mut R, chunk_number: u64, num_chunks: u64, @@ -1646,12 +1702,13 @@ where let num_chunks: usize = num_chunks.try_into().unwrap(); let chunk_number: usize = chunk_number.try_into().unwrap(); - for (i, line_result) in reader.lines().enumerate() { + let sep = settings.separator; + for (i, line_result) in reader.split(sep).enumerate() { let line = line_result?; - let bytes = line.as_bytes(); + let bytes = line.as_slice(); if (i % num_chunks) == chunk_number { writer.write_all(bytes)?; - writer.write_all(b"\n")?; + writer.write_all(&[sep])?; } } Ok(()) diff --git a/tests/by-util/test_split.rs b/tests/by-util/test_split.rs index 7c6d271e6cc..6fc3a370613 100644 --- a/tests/by-util/test_split.rs +++ b/tests/by-util/test_split.rs @@ -1483,3 +1483,242 @@ fn test_split_non_utf8_argument_windows() { .fails() .stderr_contains("error: invalid UTF-8 was detected in one or more arguments"); } + +// Test '--separator' / '-t' option following GNU tests example +// test separators: '\n' , '\0' , ';' +// test with '--lines=2' , '--line-bytes=4' , '--number=l/3' , '--number=r/3' , '--number=l/1/3' , '--number=r/1/3' +#[test] +fn test_split_separator_nl_lines() { + let (at, mut ucmd) = at_and_ucmd!(); + ucmd.args(&["--lines=2", "-t", "\n"]) + .pipe_in("1\n2\n3\n4\n5\n") + .succeeds(); + + assert_eq!(file_read(&at, "xaa"), "1\n2\n"); + assert_eq!(file_read(&at, "xab"), "3\n4\n"); + assert_eq!(file_read(&at, "xac"), "5\n"); + assert!(!at.plus("xad").exists()); +} + +#[test] +fn test_split_separator_nl_line_bytes() { + let (at, mut ucmd) = at_and_ucmd!(); + ucmd.args(&["--line-bytes=4", "-t", "\n"]) + .pipe_in("1\n2\n3\n4\n5\n") + .succeeds(); + + assert_eq!(file_read(&at, "xaa"), "1\n2\n"); + assert_eq!(file_read(&at, "xab"), "3\n4\n"); + assert_eq!(file_read(&at, "xac"), "5\n"); + assert!(!at.plus("xad").exists()); +} + +#[test] +fn test_split_separator_nl_number_l() { + let (at, mut ucmd) = at_and_ucmd!(); + ucmd.args(&["--number=l/3", "--separator=\n", "fivelines.txt"]) + .succeeds(); + + assert_eq!(file_read(&at, "xaa"), "1\n2\n"); + assert_eq!(file_read(&at, "xab"), "3\n4\n"); + assert_eq!(file_read(&at, "xac"), "5\n"); + assert!(!at.plus("xad").exists()); +} + +#[test] +fn test_split_separator_nl_number_r() { + let (at, mut ucmd) = at_and_ucmd!(); + ucmd.args(&["--number=r/3", "--separator", "\n", "fivelines.txt"]) + .succeeds(); + + assert_eq!(file_read(&at, "xaa"), "1\n4\n"); + assert_eq!(file_read(&at, "xab"), "2\n5\n"); + assert_eq!(file_read(&at, "xac"), "3\n"); + assert!(!at.plus("xad").exists()); +} + +#[test] +fn test_split_separator_nul_lines() { + let (at, mut ucmd) = at_and_ucmd!(); + ucmd.args(&["--lines=2", "-t", "\\0", "separator_nul.txt"]) + .succeeds(); + + assert_eq!(file_read(&at, "xaa"), "1\02\0"); + assert_eq!(file_read(&at, "xab"), "3\04\0"); + assert_eq!(file_read(&at, "xac"), "5\0"); + assert!(!at.plus("xad").exists()); +} + +#[test] +fn test_split_separator_nul_line_bytes() { + let (at, mut ucmd) = at_and_ucmd!(); + ucmd.args(&["--line-bytes=4", "-t", "\\0", "separator_nul.txt"]) + .succeeds(); + + assert_eq!(file_read(&at, "xaa"), "1\02\0"); + assert_eq!(file_read(&at, "xab"), "3\04\0"); + assert_eq!(file_read(&at, "xac"), "5\0"); + assert!(!at.plus("xad").exists()); +} + +#[test] +fn test_split_separator_nul_number_l() { + let (at, mut ucmd) = at_and_ucmd!(); + ucmd.args(&["--number=l/3", "--separator=\\0", "separator_nul.txt"]) + .succeeds(); + + assert_eq!(file_read(&at, "xaa"), "1\02\0"); + assert_eq!(file_read(&at, "xab"), "3\04\0"); + assert_eq!(file_read(&at, "xac"), "5\0"); + assert!(!at.plus("xad").exists()); +} + +#[test] +fn test_split_separator_nul_number_r() { + let (at, mut ucmd) = at_and_ucmd!(); + ucmd.args(&["--number=r/3", "--separator=\\0", "separator_nul.txt"]) + .succeeds(); + + assert_eq!(file_read(&at, "xaa"), "1\04\0"); + assert_eq!(file_read(&at, "xab"), "2\05\0"); + assert_eq!(file_read(&at, "xac"), "3\0"); + assert!(!at.plus("xad").exists()); +} + +#[test] +fn test_split_separator_semicolon_lines() { + let (at, mut ucmd) = at_and_ucmd!(); + ucmd.args(&["--lines=2", "-t", ";", "separator_semicolon.txt"]) + .succeeds(); + + assert_eq!(file_read(&at, "xaa"), "1;2;"); + assert_eq!(file_read(&at, "xab"), "3;4;"); + assert_eq!(file_read(&at, "xac"), "5;"); + assert!(!at.plus("xad").exists()); +} + +#[test] +fn test_split_separator_semicolon_line_bytes() { + let (at, mut ucmd) = at_and_ucmd!(); + ucmd.args(&["--line-bytes=4", "-t", ";", "separator_semicolon.txt"]) + .succeeds(); + + assert_eq!(file_read(&at, "xaa"), "1;2;"); + assert_eq!(file_read(&at, "xab"), "3;4;"); + assert_eq!(file_read(&at, "xac"), "5;"); + assert!(!at.plus("xad").exists()); +} + +#[test] +fn test_split_separator_semicolon_number_l() { + let (at, mut ucmd) = at_and_ucmd!(); + ucmd.args(&["--number=l/3", "--separator=;", "separator_semicolon.txt"]) + .succeeds(); + + assert_eq!(file_read(&at, "xaa"), "1;2;"); + assert_eq!(file_read(&at, "xab"), "3;4;"); + assert_eq!(file_read(&at, "xac"), "5;"); + assert!(!at.plus("xad").exists()); +} + +#[test] +fn test_split_separator_semicolon_number_r() { + let (at, mut ucmd) = at_and_ucmd!(); + ucmd.args(&["--number=r/3", "--separator=;", "separator_semicolon.txt"]) + .succeeds(); + + assert_eq!(file_read(&at, "xaa"), "1;4;"); + assert_eq!(file_read(&at, "xab"), "2;5;"); + assert_eq!(file_read(&at, "xac"), "3;"); + assert!(!at.plus("xad").exists()); +} + +#[test] +fn test_split_separator_semicolon_number_kth_l() { + new_ucmd!() + .args(&[ + "--number=l/1/3", + "--separator", + ";", + "separator_semicolon.txt", + ]) + .succeeds() + .stdout_only("1;2;"); +} + +#[test] +fn test_split_separator_semicolon_number_kth_r() { + new_ucmd!() + .args(&[ + "--number=r/1/3", + "--separator", + ";", + "separator_semicolon.txt", + ]) + .succeeds() + .stdout_only("1;4;"); +} + +// Test error edge cases for separator option +#[test] +fn test_split_separator_no_value() { + new_ucmd!() + .args(&["-t"]) + .ignore_stdin_write_error() + .pipe_in("a\n") + .fails() + .stderr_contains( + "error: a value is required for '--separator ' but none was supplied", + ); +} + +#[test] +fn test_split_separator_invalid_usage() { + let scene = TestScenario::new(util_name!()); + scene + .ucmd() + .args(&["--separator=xx"]) + .ignore_stdin_write_error() + .pipe_in("a\n") + .fails() + .no_stdout() + .stderr_contains("split: multi-character separator 'xx'"); + scene + .ucmd() + .args(&["-ta", "-tb"]) + .ignore_stdin_write_error() + .pipe_in("a\n") + .fails() + .no_stdout() + .stderr_contains("split: multiple separator characters specified"); + scene + .ucmd() + .args(&["-t'\n'", "-tb"]) + .ignore_stdin_write_error() + .pipe_in("a\n") + .fails() + .no_stdout() + .stderr_contains("split: multiple separator characters specified"); +} + +// Test using same separator multiple times +#[test] +fn test_split_separator_same_multiple() { + let scene = TestScenario::new(util_name!()); + scene + .ucmd() + .args(&["--separator=:", "--separator=:", "fivelines.txt"]) + .succeeds(); + scene + .ucmd() + .args(&["-t:", "--separator=:", "fivelines.txt"]) + .succeeds(); + scene + .ucmd() + .args(&["-t", ":", "-t", ":", "fivelines.txt"]) + .succeeds(); + scene + .ucmd() + .args(&["-t:", "-t:", "-t,", "fivelines.txt"]) + .fails(); +} diff --git a/tests/fixtures/split/separator_nul.txt b/tests/fixtures/split/separator_nul.txt new file mode 100644 index 0000000000000000000000000000000000000000..c4c49609dbd79d27ec1457c489741e63995dc080 GIT binary patch literal 10 RcmXqHFk&!fFkvud000H>0RR91 literal 0 HcmV?d00001 diff --git a/tests/fixtures/split/separator_semicolon.txt b/tests/fixtures/split/separator_semicolon.txt new file mode 100644 index 00000000000..a8396d8eef5 --- /dev/null +++ b/tests/fixtures/split/separator_semicolon.txt @@ -0,0 +1 @@ +1;2;3;4;5; \ No newline at end of file From 252d01ac33b2eb4fdd0640a07840d0ef3318f886 Mon Sep 17 00:00:00 2001 From: PGIII Date: Mon, 2 Oct 2023 18:48:22 -0400 Subject: [PATCH 0155/2851] cp: fail when trying to copy to read only file on mac (#5261) * cp: fail when trying to copy to read only file * fix spelling error in macos.rs * simplify permissions check * add comment * fix typo --- src/uu/cp/src/platform/macos.rs | 11 +++++++++-- tests/by-util/test_cp.rs | 16 ++++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/uu/cp/src/platform/macos.rs b/src/uu/cp/src/platform/macos.rs index 8c62c78d95d..77bdbbbdb83 100644 --- a/src/uu/cp/src/platform/macos.rs +++ b/src/uu/cp/src/platform/macos.rs @@ -63,8 +63,15 @@ pub(crate) fn copy_on_write( { // clonefile(2) fails if the destination exists. Remove it and try again. Do not // bother to check if removal worked because we're going to try to clone again. - let _ = fs::remove_file(dest); - error = pfn(src.as_ptr(), dst.as_ptr(), 0); + // first lets make sure the dest file is not read only + if fs::metadata(dest).map_or(false, |md| !md.permissions().readonly()) { + // remove and copy again + // TODO: rewrite this to better match linux behavior + // linux first opens the source file and destination file then uses the file + // descriptors to do the clone. + let _ = fs::remove_file(dest); + error = pfn(src.as_ptr(), dst.as_ptr(), 0); + } } } } diff --git a/tests/by-util/test_cp.rs b/tests/by-util/test_cp.rs index 1ce74572d5d..fe56d11601e 100644 --- a/tests/by-util/test_cp.rs +++ b/tests/by-util/test_cp.rs @@ -3440,3 +3440,19 @@ fn test_cp_only_source_no_target() { panic!("Failure: stderr was \n{stderr_str}"); } } + +#[test] +fn test_cp_dest_no_permissions() { + let ts = TestScenario::new(util_name!()); + let at = &ts.fixtures; + + at.touch("valid.txt"); + at.touch("invalid_perms.txt"); + at.set_readonly("invalid_perms.txt"); + + ts.ucmd() + .args(&["valid.txt", "invalid_perms.txt"]) + .fails() + .stderr_contains("invalid_perms.txt") + .stderr_contains("denied"); +} From a107374c8286796b170fbabbd65a6a28656a7415 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Tue, 3 Oct 2023 12:10:20 +0200 Subject: [PATCH 0156/2851] echo: use controlflow instead of bool --- src/uu/echo/src/echo.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/uu/echo/src/echo.rs b/src/uu/echo/src/echo.rs index 5ccc6a32a95..94788721004 100644 --- a/src/uu/echo/src/echo.rs +++ b/src/uu/echo/src/echo.rs @@ -6,6 +6,7 @@ use clap::{crate_version, Arg, ArgAction, Command}; use std::io::{self, Write}; use std::iter::Peekable; +use std::ops::ControlFlow; use std::str::Chars; use uucore::error::{FromIo, UResult}; use uucore::{format_usage, help_about, help_section, help_usage}; @@ -62,7 +63,7 @@ fn parse_code(input: &mut Peekable, base: Base) -> Option { Some(ret.into()) } -fn print_escaped(input: &str, mut output: impl Write) -> io::Result { +fn print_escaped(input: &str, mut output: impl Write) -> io::Result> { let mut iter = input.chars().peekable(); while let Some(c) = iter.next() { if c != '\\' { @@ -85,7 +86,7 @@ fn print_escaped(input: &str, mut output: impl Write) -> io::Result { '\\' => '\\', 'a' => '\x07', 'b' => '\x08', - 'c' => return Ok(true), + 'c' => return Ok(ControlFlow::Break(())), 'e' => '\x1b', 'f' => '\x0c', 'n' => '\n', @@ -112,7 +113,7 @@ fn print_escaped(input: &str, mut output: impl Write) -> io::Result { } } - Ok(false) + Ok(ControlFlow::Continue(())) } #[uucore::main] @@ -173,8 +174,7 @@ fn execute(no_newline: bool, escaped: bool, free: &[String]) -> io::Result<()> { write!(output, " ")?; } if escaped { - let should_stop = print_escaped(input, &mut output)?; - if should_stop { + if print_escaped(input, &mut output)?.is_break() { break; } } else { From c29bcb219f6d10028ba5b9137730f45c158f93b1 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Tue, 3 Oct 2023 14:37:15 +0200 Subject: [PATCH 0157/2851] echo: don't output "\n" if "\c" is encountered --- src/uu/echo/src/echo.rs | 2 +- tests/by-util/test_echo.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/uu/echo/src/echo.rs b/src/uu/echo/src/echo.rs index 94788721004..b3707b6f898 100644 --- a/src/uu/echo/src/echo.rs +++ b/src/uu/echo/src/echo.rs @@ -175,7 +175,7 @@ fn execute(no_newline: bool, escaped: bool, free: &[String]) -> io::Result<()> { } if escaped { if print_escaped(input, &mut output)?.is_break() { - break; + return Ok(()); } } else { write!(output, "{input}")?; diff --git a/tests/by-util/test_echo.rs b/tests/by-util/test_echo.rs index dce5a4c9520..875ff66cb14 100644 --- a/tests/by-util/test_echo.rs +++ b/tests/by-util/test_echo.rs @@ -122,7 +122,7 @@ fn test_escape_no_further_output() { new_ucmd!() .args(&["-e", "a\\cb", "c"]) .succeeds() - .stdout_only("a\n"); + .stdout_only("a"); } #[test] From 54ba81ecbb9a810d3672ae6e378effb5c0dcb352 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Tue, 3 Oct 2023 15:09:20 +0200 Subject: [PATCH 0158/2851] mv: fix typo in test function name test_mv_info_self -> test_mv_into_self --- tests/by-util/test_mv.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/by-util/test_mv.rs b/tests/by-util/test_mv.rs index 0490d41193f..f7f9622f52e 100644 --- a/tests/by-util/test_mv.rs +++ b/tests/by-util/test_mv.rs @@ -1323,7 +1323,7 @@ fn test_mv_interactive_error() { } #[test] -fn test_mv_info_self() { +fn test_mv_into_self() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; let dir1 = "dir1"; From b591bedcab64851ec8d5091cc092db1144687369 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 3 Oct 2023 15:34:37 +0000 Subject: [PATCH 0159/2851] chore(deps): update rust crate memmap2 to 0.9 --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 76e1dd72c31..33ee7ff1477 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1291,9 +1291,9 @@ checksum = "5486aed0026218e61b8a01d5fbd5a0a134649abb71a0e53b7bc088529dced86e" [[package]] name = "memmap2" -version = "0.8.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a5a03cefb0d953ec0be133036f14e109412fa594edc2f77227249db66cc3ed" +checksum = "deaba38d7abf1d4cca21cc89e932e542ba2b9258664d2a9ef0e61512039c9375" dependencies = [ "libc", ] diff --git a/Cargo.toml b/Cargo.toml index c4c3fc2ff0a..e7fc2851bd8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -292,7 +292,7 @@ lscolors = { version = "0.15.0", default-features = false, features = [ "nu-ansi-term", ] } memchr = "2" -memmap2 = "0.8" +memmap2 = "0.9" nix = { version = "0.27", default-features = false } nom = "7.1.3" notify = { version = "=6.0.1", features = ["macos_kqueue"] } From 6c30a1df78704773257f9f1272b2e0d492a3feec Mon Sep 17 00:00:00 2001 From: tommady Date: Wed, 4 Oct 2023 06:04:46 +0000 Subject: [PATCH 0160/2851] fix-5327 --- src/uu/cp/src/cp.rs | 59 ++++++++++++++++++++++++++++++--------------- 1 file changed, 40 insertions(+), 19 deletions(-) diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index b6270719cc9..285a696a912 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -184,7 +184,7 @@ pub struct Attributes { #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Preserve { - No, + No { explicit: bool }, Yes { required: bool }, } @@ -197,9 +197,9 @@ impl PartialOrd for Preserve { impl Ord for Preserve { fn cmp(&self, other: &Self) -> Ordering { match (self, other) { - (Self::No, Self::No) => Ordering::Equal, - (Self::Yes { .. }, Self::No) => Ordering::Greater, - (Self::No, Self::Yes { .. }) => Ordering::Less, + (Self::No { .. }, Self::No { .. }) => Ordering::Equal, + (Self::Yes { .. }, Self::No { .. }) => Ordering::Greater, + (Self::No { .. }, Self::Yes { .. }) => Ordering::Less, ( Self::Yes { required: req_self }, Self::Yes { @@ -800,7 +800,7 @@ impl Attributes { } #[cfg(not(feature = "feat_selinux"))] { - Preserve::No + Preserve::No { explicit: false } } }, links: Preserve::Yes { required: true }, @@ -809,12 +809,12 @@ impl Attributes { pub const NONE: Self = Self { #[cfg(unix)] - ownership: Preserve::No, - mode: Preserve::No, - timestamps: Preserve::No, - context: Preserve::No, - links: Preserve::No, - xattr: Preserve::No, + ownership: Preserve::No { explicit: false }, + mode: Preserve::No { explicit: false }, + timestamps: Preserve::No { explicit: false }, + context: Preserve::No { explicit: false }, + links: Preserve::No { explicit: false }, + xattr: Preserve::No { explicit: false }, }; // TODO: ownership is required if the user is root, for non-root users it's not required. @@ -954,7 +954,9 @@ impl Options { if attribute_strs.len() > 0 { let no_preserve_attributes = Attributes::parse_iter(attribute_strs)?; if matches!(no_preserve_attributes.links, Preserve::Yes { .. }) { - attributes.links = Preserve::No; + attributes.links = Preserve::No { explicit: true }; + } else if matches!(no_preserve_attributes.mode, Preserve::Yes { .. }) { + attributes.mode = Preserve::No { explicit: true }; } } } @@ -1050,11 +1052,21 @@ impl Options { fn preserve_hard_links(&self) -> bool { match self.attributes.links { - Preserve::No => false, + Preserve::No { .. } => false, Preserve::Yes { .. } => true, } } + fn preserve_mode(&self) -> (bool, bool) { + match self.attributes.mode { + Preserve::No { explicit } => match explicit { + true => (false, true), + false => (false, false), + }, + Preserve::Yes { .. } => (true, false), + } + } + /// Whether to force overwriting the destination file. fn force(&self) -> bool { matches!(self.overwrite, OverwriteMode::Clobber(ClobberMode::Force)) @@ -1306,7 +1318,7 @@ impl OverwriteMode { /// If it's required, then the error is thrown. fn handle_preserve CopyResult<()>>(p: &Preserve, f: F) -> CopyResult<()> { match p { - Preserve::No => {} + Preserve::No { .. } => {} Preserve::Yes { required } => { let result = f(); if *required { @@ -1735,15 +1747,24 @@ fn copy_file( let mut permissions = source_metadata.permissions(); #[cfg(unix)] { - use uucore::mode::get_umask; - let mut mode = permissions.mode(); - // remove sticky bit, suid and gid bit - const SPECIAL_PERMS_MASK: u32 = 0o7000; - mode &= !SPECIAL_PERMS_MASK; + let (is_preserve_mode, is_explicit_no_preserve_mode) = options.preserve_mode(); + if !is_preserve_mode { + use libc::{ + S_IRGRP, S_IROTH, S_IRUSR, S_IRWXG, S_IRWXO, S_IRWXU, S_IWGRP, S_IWOTH, S_IWUSR, + }; + const MODE_RW_UGO: u32 = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; + const S_IRWXUGO: u32 = S_IRWXU | S_IRWXG | S_IRWXO; + + match is_explicit_no_preserve_mode { + true => mode = MODE_RW_UGO, + false => mode &= S_IRWXUGO, + } + } // apply umask + use uucore::mode::get_umask; mode &= !get_umask(); permissions.set_mode(mode); From 88f88e51cc54c8f53dc7d24d59d84508cf1c5fb1 Mon Sep 17 00:00:00 2001 From: tommady Date: Wed, 4 Oct 2023 07:12:25 +0000 Subject: [PATCH 0161/2851] fix expected , found and spelling errors --- src/uu/cp/src/cp.rs | 8 +++++--- tests/by-util/test_cp.rs | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index 285a696a912..fc6332f3bee 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -2,7 +2,7 @@ // // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// spell-checker:ignore (ToDO) copydir ficlone fiemap ftruncate linkgs lstat nlink nlinks pathbuf pwrite reflink strs xattrs symlinked deduplicated advcpmv nushell +// spell-checker:ignore (ToDO) copydir ficlone fiemap ftruncate linkgs lstat nlink nlinks pathbuf pwrite reflink strs xattrs symlinked deduplicated advcpmv nushell IRWXG IRWXO IRWXU IRWXUGO IRWXU IRWXG IRWXO IRWXUGO #![allow(clippy::missing_safety_doc)] #![allow(clippy::extra_unused_lifetimes)] @@ -1057,6 +1057,7 @@ impl Options { } } + #[cfg(unix)] fn preserve_mode(&self) -> (bool, bool) { match self.attributes.mode { Preserve::No { explicit } => match explicit { @@ -1754,8 +1755,9 @@ fn copy_file( use libc::{ S_IRGRP, S_IROTH, S_IRUSR, S_IRWXG, S_IRWXO, S_IRWXU, S_IWGRP, S_IWOTH, S_IWUSR, }; - const MODE_RW_UGO: u32 = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; - const S_IRWXUGO: u32 = S_IRWXU | S_IRWXG | S_IRWXO; + const MODE_RW_UGO: u32 = + (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH) as u32; + const S_IRWXUGO: u32 = (S_IRWXU | S_IRWXG | S_IRWXO) as u32; match is_explicit_no_preserve_mode { true => mode = MODE_RW_UGO, diff --git a/tests/by-util/test_cp.rs b/tests/by-util/test_cp.rs index f6d339a7ebc..95d130f880e 100644 --- a/tests/by-util/test_cp.rs +++ b/tests/by-util/test_cp.rs @@ -2,7 +2,7 @@ // // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// spell-checker:ignore (flags) reflink (fs) tmpfs (linux) rlimit Rlim NOFILE clob btrfs ROOTDIR USERDIR procfs outfile +// spell-checker:ignore (flags) reflink (fs) tmpfs (linux) rlimit Rlim NOFILE clob btrfs ROOTDIR USERDIR procfs outfile uufs use crate::common::util::TestScenario; #[cfg(not(windows))] From cdde57608c028a8a678a4b55464589a299bcc4d0 Mon Sep 17 00:00:00 2001 From: tommady Date: Wed, 4 Oct 2023 08:06:30 +0000 Subject: [PATCH 0162/2851] fix macos mode_t is u16 but other unix platforms are u32 --- src/uu/cp/src/cp.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index fc6332f3bee..e0a7984f2de 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -1755,8 +1755,16 @@ fn copy_file( use libc::{ S_IRGRP, S_IROTH, S_IRUSR, S_IRWXG, S_IRWXO, S_IRWXU, S_IWGRP, S_IWOTH, S_IWUSR, }; + + #[cfg(not(macos))] + const MODE_RW_UGO: u32 = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; + #[cfg(not(macos))] + const S_IRWXUGO: u32 = S_IRWXU | S_IRWXG | S_IRWXO; + + #[cfg(macos)] const MODE_RW_UGO: u32 = (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH) as u32; + #[cfg(macos)] const S_IRWXUGO: u32 = (S_IRWXU | S_IRWXG | S_IRWXO) as u32; match is_explicit_no_preserve_mode { From 74c393974c7c5582d2251a54b68805e5eb1dbf0a Mon Sep 17 00:00:00 2001 From: tommady Date: Wed, 4 Oct 2023 08:17:40 +0000 Subject: [PATCH 0163/2851] fix android mode_t is u16 but other unix platforms are u32 --- src/uu/cp/src/cp.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index c45436b8d1e..c3ad34118f5 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -1749,15 +1749,15 @@ fn copy_file( S_IRGRP, S_IROTH, S_IRUSR, S_IRWXG, S_IRWXO, S_IRWXU, S_IWGRP, S_IWOTH, S_IWUSR, }; - #[cfg(not(macos))] + #[cfg(not(any(target_os = "android", target_os = "macos")))] const MODE_RW_UGO: u32 = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; - #[cfg(not(macos))] + #[cfg(not(any(target_os = "android", target_os = "macos")))] const S_IRWXUGO: u32 = S_IRWXU | S_IRWXG | S_IRWXO; - #[cfg(macos)] + #[cfg(any(target_os = "android", target_os = "macos"))] const MODE_RW_UGO: u32 = (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH) as u32; - #[cfg(macos)] + #[cfg(any(target_os = "android", target_os = "macos"))] const S_IRWXUGO: u32 = (S_IRWXU | S_IRWXG | S_IRWXO) as u32; match is_explicit_no_preserve_mode { From 6c05385d77454a2cfe89bd371d3fb2af17b3bfc4 Mon Sep 17 00:00:00 2001 From: tommady Date: Wed, 4 Oct 2023 08:31:07 +0000 Subject: [PATCH 0164/2851] fix macos-12 mode_t is u16 but other unix platforms are u32 --- src/uu/cp/src/cp.rs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index c3ad34118f5..1a9d5059da7 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -1749,15 +1749,23 @@ fn copy_file( S_IRGRP, S_IROTH, S_IRUSR, S_IRWXG, S_IRWXO, S_IRWXU, S_IWGRP, S_IWOTH, S_IWUSR, }; - #[cfg(not(any(target_os = "android", target_os = "macos")))] + #[cfg(not(any( + target_os = "android", + target_os = "macos", + target_os = "macos-12" + )))] const MODE_RW_UGO: u32 = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; - #[cfg(not(any(target_os = "android", target_os = "macos")))] + #[cfg(not(any( + target_os = "android", + target_os = "macos", + target_os = "macos-12" + )))] const S_IRWXUGO: u32 = S_IRWXU | S_IRWXG | S_IRWXO; - #[cfg(any(target_os = "android", target_os = "macos"))] + #[cfg(any(target_os = "android", target_os = "macos", target_os = "macos-12"))] const MODE_RW_UGO: u32 = (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH) as u32; - #[cfg(any(target_os = "android", target_os = "macos"))] + #[cfg(any(target_os = "android", target_os = "macos", target_os = "macos-12"))] const S_IRWXUGO: u32 = (S_IRWXU | S_IRWXG | S_IRWXO) as u32; match is_explicit_no_preserve_mode { From 5ce372082077bad27f2857979e1a09427f97d921 Mon Sep 17 00:00:00 2001 From: tommady Date: Wed, 4 Oct 2023 09:12:26 +0000 Subject: [PATCH 0165/2851] fix freebds mode_t is u16 but other unix platforms are u32 --- src/uu/cp/src/cp.rs | 20 ++++++++++++++++---- tests/by-util/test_cp.rs | 2 +- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index 1a9d5059da7..f8c5ef3ce5f 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -1752,20 +1752,32 @@ fn copy_file( #[cfg(not(any( target_os = "android", target_os = "macos", - target_os = "macos-12" + target_os = "macos-12", + target_os = "freebds", )))] const MODE_RW_UGO: u32 = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; #[cfg(not(any( target_os = "android", target_os = "macos", - target_os = "macos-12" + target_os = "macos-12", + target_os = "freebds", )))] const S_IRWXUGO: u32 = S_IRWXU | S_IRWXG | S_IRWXO; - #[cfg(any(target_os = "android", target_os = "macos", target_os = "macos-12"))] + #[cfg(any( + target_os = "android", + target_os = "macos", + target_os = "macos-12", + target_os = "freebds", + ))] const MODE_RW_UGO: u32 = (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH) as u32; - #[cfg(any(target_os = "android", target_os = "macos", target_os = "macos-12"))] + #[cfg(any( + target_os = "android", + target_os = "macos", + target_os = "macos-12", + target_os = "freebds", + ))] const S_IRWXUGO: u32 = (S_IRWXU | S_IRWXG | S_IRWXO) as u32; match is_explicit_no_preserve_mode { diff --git a/tests/by-util/test_cp.rs b/tests/by-util/test_cp.rs index e85cd0c5761..96b47cd43a7 100644 --- a/tests/by-util/test_cp.rs +++ b/tests/by-util/test_cp.rs @@ -1553,7 +1553,7 @@ fn test_cp_preserve_links_case_7() { } #[test] -#[cfg(all(unix, not(target_os = "freebsd")))] +#[cfg(all(unix))] fn test_cp_no_preserve_mode_case() { use libc::umask; use uucore::fs as uufs; From aaea3b40fba455156bf927746e5076d0de30a686 Mon Sep 17 00:00:00 2001 From: tommady Date: Wed, 4 Oct 2023 09:16:54 +0000 Subject: [PATCH 0166/2851] fix freebsd typo --- src/uu/cp/src/cp.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index f8c5ef3ce5f..72431cc12b5 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -1753,14 +1753,14 @@ fn copy_file( target_os = "android", target_os = "macos", target_os = "macos-12", - target_os = "freebds", + target_os = "freebsd", )))] const MODE_RW_UGO: u32 = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; #[cfg(not(any( target_os = "android", target_os = "macos", target_os = "macos-12", - target_os = "freebds", + target_os = "freebsd", )))] const S_IRWXUGO: u32 = S_IRWXU | S_IRWXG | S_IRWXO; @@ -1768,7 +1768,7 @@ fn copy_file( target_os = "android", target_os = "macos", target_os = "macos-12", - target_os = "freebds", + target_os = "freebsd", ))] const MODE_RW_UGO: u32 = (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH) as u32; @@ -1776,7 +1776,7 @@ fn copy_file( target_os = "android", target_os = "macos", target_os = "macos-12", - target_os = "freebds", + target_os = "freebsd", ))] const S_IRWXUGO: u32 = (S_IRWXU | S_IRWXG | S_IRWXO) as u32; From e88183174b9592c6a490658f0ef09938459ffcdd Mon Sep 17 00:00:00 2001 From: boxdot Date: Wed, 4 Oct 2023 16:12:02 +0200 Subject: [PATCH 0167/2851] relpath: remove Closes #5236 --- Cargo.lock | 9 -- Cargo.toml | 2 - GNUmakefile | 1 - docs/compiles_table.csv | 42 ++++---- src/uu/relpath/Cargo.toml | 23 ----- src/uu/relpath/LICENSE | 1 - src/uu/relpath/relpath.md | 8 -- src/uu/relpath/src/main.rs | 1 - src/uu/relpath/src/relpath.rs | 91 ---------------- tests/by-util/test_relpath.rs | 189 ---------------------------------- tests/tests.rs | 4 - util/show-utils.BAT | 4 +- util/show-utils.sh | 4 +- 13 files changed, 25 insertions(+), 354 deletions(-) delete mode 100644 src/uu/relpath/Cargo.toml delete mode 120000 src/uu/relpath/LICENSE delete mode 100644 src/uu/relpath/relpath.md delete mode 100644 src/uu/relpath/src/main.rs delete mode 100644 src/uu/relpath/src/relpath.rs delete mode 100644 tests/by-util/test_relpath.rs diff --git a/Cargo.lock b/Cargo.lock index 33ee7ff1477..252be5d676e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -470,7 +470,6 @@ dependencies = [ "uu_pwd", "uu_readlink", "uu_realpath", - "uu_relpath", "uu_rm", "uu_rmdir", "uu_runcon", @@ -2866,14 +2865,6 @@ dependencies = [ "uucore", ] -[[package]] -name = "uu_relpath" -version = "0.0.21" -dependencies = [ - "clap", - "uucore", -] - [[package]] name = "uu_rm" version = "0.0.21" diff --git a/Cargo.toml b/Cargo.toml index e7fc2851bd8..88bd1635b9a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -100,7 +100,6 @@ feat_common_core = [ "pwd", "readlink", "realpath", - "relpath", "rm", "rmdir", "seq", @@ -430,7 +429,6 @@ ptx = { optional = true, version = "0.0.21", package = "uu_ptx", path = "src/uu/ pwd = { optional = true, version = "0.0.21", package = "uu_pwd", path = "src/uu/pwd" } readlink = { optional = true, version = "0.0.21", package = "uu_readlink", path = "src/uu/readlink" } realpath = { optional = true, version = "0.0.21", package = "uu_realpath", path = "src/uu/realpath" } -relpath = { optional = true, version = "0.0.21", package = "uu_relpath", path = "src/uu/relpath" } rm = { optional = true, version = "0.0.21", package = "uu_rm", path = "src/uu/rm" } rmdir = { optional = true, version = "0.0.21", package = "uu_rmdir", path = "src/uu/rmdir" } runcon = { optional = true, version = "0.0.21", package = "uu_runcon", path = "src/uu/runcon" } diff --git a/GNUmakefile b/GNUmakefile index c672458a1cd..ad2d38081f3 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -102,7 +102,6 @@ PROGS := \ pwd \ readlink \ realpath \ - relpath \ rm \ rmdir \ seq \ diff --git a/docs/compiles_table.csv b/docs/compiles_table.csv index d18854e0e91..e263067b78d 100644 --- a/docs/compiles_table.csv +++ b/docs/compiles_table.csv @@ -1,21 +1,21 @@ -target,arch,base32,base64,basename,cat,chgrp,chmod,chown,chroot,cksum,comm,cp,csplit,cut,date,df,dircolors,dirname,du,echo,env,expand,expr,factor,false,fmt,fold,groups,hashsum,head,hostid,hostname,id,install,join,kill,link,ln,logname,ls,mkdir,mkfifo,mknod,mktemp,more,mv,nice,nl,nohup,nproc,numfmt,od,paste,pathchk,pinky,printenv,printf,ptx,pwd,readlink,realpath,relpath,rm,rmdir,seq,shred,shuf,sleep,sort,split,stat,stdbuf,sum,sync,tac,tail,tee,test,timeout,touch,tr,true,truncate,tsort,tty,uname,unexpand,uniq,unlink,uptime,users,wc,who,whoami,yes,chcon,pr,dir,vdir,dd,basenc,runcon -aarch64-unknown-linux-gnu,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 -i686-unknown-linux-gnu,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 -powerpc64-unknown-linux-gnu,0,0,0,0,0,0,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 -riscv64gc-unknown-linux-gnu,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101 -x86_64-unknown-linux-gnu,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,101,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 -aarch64-pc-windows-msvc,0,0,0,0,0,101,101,101,101,0,0,0,0,0,0,0,0,0,101,0,0,0,101,0,0,0,0,101,0,0,0,0,101,101,0,101,0,0,0,101,0,101,101,0,0,0,101,0,101,0,0,0,0,101,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,101,101,0,0,0,0,0,0,101,0,0,0,0,0,101,0,0,0,101,0,101,0,101,101,0,0,0,0,0,0,0,0 -i686-pc-windows-gnu,0,0,0,0,0,101,101,101,101,0,0,0,0,0,0,0,0,0,101,0,0,0,101,0,0,0,0,101,0,0,0,0,101,101,0,101,0,0,0,0,0,101,101,0,0,0,101,0,101,0,0,0,0,101,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,101,101,0,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,101,0,101,0,101,0,0,0,0,0,0,0,0,0 -i686-pc-windows-msvc,0,0,0,0,0,101,101,101,101,0,0,0,0,0,0,0,0,0,101,0,0,0,101,0,0,0,0,101,0,0,0,0,101,101,0,101,0,0,0,0,0,101,101,0,0,0,101,0,101,0,0,0,0,101,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,101,101,0,0,0,0,0,0,101,0,0,0,0,0,101,0,0,0,101,0,101,0,101,0,0,0,0,0,0,0,0,0 -x86_64-pc-windows-gnu,0,0,0,0,0,101,101,101,101,0,0,0,0,0,0,0,0,0,101,0,0,0,101,0,0,0,0,101,0,0,0,0,101,101,0,101,0,0,0,0,0,101,101,0,0,0,101,0,101,0,0,0,0,101,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,101,101,0,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,101,0,101,0,101,0,0,0,0,0,0,0,0,0 -x86_64-pc-windows-msvc,0,0,0,0,0,101,101,101,101,0,0,0,0,0,0,0,0,0,101,0,0,0,101,0,0,0,0,101,0,0,0,0,101,101,0,101,0,0,0,0,0,101,101,0,0,0,101,0,101,0,0,0,0,101,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,101,101,0,0,0,0,0,0,101,0,0,0,0,0,101,0,0,0,101,0,101,0,101,0,0,0,0,0,0,0,0,0 -x86_64-apple-darwin,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 -x86_64-unknown-freebsd,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 -x86_64-unknown-netbsd,0,0,0,0,0,0,0,0,101,0,0,0,0,0,0,101,0,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,101,0,0,101,0,0,0,0,0,0,0,0,0,0,0,0,101,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,101,0,0,0,0,0,0,101,0,0,0,101,101,0,101,0,0,0,0,0,0,0,0,0 -aarch64-linux-android,0,0,0,0,0,0,0,0,101,0,0,0,0,0,0,101,0,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,101,0,0,101,0,0,0,0,0,0,0,0,0,0,0,0,101,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,101,0,0,101,0,0,0,0,101,0,0,0,0,0,0,101,0,0,0,101,101,0,101,0,0,0,0,0,0,0,0,0 -x86_64-linux-android,0,0,0,0,0,0,0,0,101,0,0,0,0,0,0,101,0,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,101,0,0,101,0,0,0,0,0,0,0,0,0,0,0,0,101,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,101,0,0,101,0,0,0,0,101,0,0,0,0,0,0,101,0,0,0,101,101,0,101,0,0,0,0,0,0,0,0,0 -x86_64-sun-solaris,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101 -wasm32-wasi,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101 -x86_64-unknown-redox,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101 -aarch64-fuchsia,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101 -x86_64-fuchsia,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101 +target,arch,base32,base64,basename,cat,chgrp,chmod,chown,chroot,cksum,comm,cp,csplit,cut,date,df,dircolors,dirname,du,echo,env,expand,expr,factor,false,fmt,fold,groups,hashsum,head,hostid,hostname,id,install,join,kill,link,ln,logname,ls,mkdir,mkfifo,mknod,mktemp,more,mv,nice,nl,nohup,nproc,numfmt,od,paste,pathchk,pinky,printenv,printf,ptx,pwd,readlink,realpath,rm,rmdir,seq,shred,shuf,sleep,sort,split,stat,stdbuf,sum,sync,tac,tail,tee,test,timeout,touch,tr,true,truncate,tsort,tty,uname,unexpand,uniq,unlink,uptime,users,wc,who,whoami,yes,chcon,pr,dir,vdir,dd,basenc,runcon +aarch64-unknown-linux-gnu,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +i686-unknown-linux-gnu,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +powerpc64-unknown-linux-gnu,0,0,0,0,0,0,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +riscv64gc-unknown-linux-gnu,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101 +x86_64-unknown-linux-gnu,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,101,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +aarch64-pc-windows-msvc,0,0,0,0,0,101,101,101,101,0,0,0,0,0,0,0,0,0,101,0,0,0,101,0,0,0,0,101,0,0,0,0,101,101,0,101,0,0,0,101,0,101,101,0,0,0,101,0,101,0,0,0,0,101,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,101,101,0,0,0,0,0,0,101,0,0,0,0,0,101,0,0,0,101,0,101,0,101,101,0,0,0,0,0,0,0,0 +i686-pc-windows-gnu,0,0,0,0,0,101,101,101,101,0,0,0,0,0,0,0,0,0,101,0,0,0,101,0,0,0,0,101,0,0,0,0,101,101,0,101,0,0,0,0,0,101,101,0,0,0,101,0,101,0,0,0,0,101,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,101,101,0,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,101,0,101,0,101,0,0,0,0,0,0,0,0,0 +i686-pc-windows-msvc,0,0,0,0,0,101,101,101,101,0,0,0,0,0,0,0,0,0,101,0,0,0,101,0,0,0,0,101,0,0,0,0,101,101,0,101,0,0,0,0,0,101,101,0,0,0,101,0,101,0,0,0,0,101,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,101,101,0,0,0,0,0,0,101,0,0,0,0,0,101,0,0,0,101,0,101,0,101,0,0,0,0,0,0,0,0,0 +x86_64-pc-windows-gnu,0,0,0,0,0,101,101,101,101,0,0,0,0,0,0,0,0,0,101,0,0,0,101,0,0,0,0,101,0,0,0,0,101,101,0,101,0,0,0,0,0,101,101,0,0,0,101,0,101,0,0,0,0,101,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,101,101,0,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,101,0,101,0,101,0,0,0,0,0,0,0,0,0 +x86_64-pc-windows-msvc,0,0,0,0,0,101,101,101,101,0,0,0,0,0,0,0,0,0,101,0,0,0,101,0,0,0,0,101,0,0,0,0,101,101,0,101,0,0,0,0,0,101,101,0,0,0,101,0,101,0,0,0,0,101,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,101,101,0,0,0,0,0,0,101,0,0,0,0,0,101,0,0,0,101,0,101,0,101,0,0,0,0,0,0,0,0,0 +x86_64-apple-darwin,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +x86_64-unknown-freebsd,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +x86_64-unknown-netbsd,0,0,0,0,0,0,0,0,101,0,0,0,0,0,0,101,0,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,101,0,0,101,0,0,0,0,0,0,0,0,0,0,0,0,101,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,101,0,0,0,0,0,0,101,0,0,0,101,101,0,101,0,0,0,0,0,0,0,0,0 +aarch64-linux-android,0,0,0,0,0,0,0,0,101,0,0,0,0,0,0,101,0,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,101,0,0,101,0,0,0,0,0,0,0,0,0,0,0,0,101,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,101,0,0,101,0,0,0,0,101,0,0,0,0,0,0,101,0,0,0,101,101,0,101,0,0,0,0,0,0,0,0,0 +x86_64-linux-android,0,0,0,0,0,0,0,0,101,0,0,0,0,0,0,101,0,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,101,0,0,101,0,0,0,0,0,0,0,0,0,0,0,0,101,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,101,0,0,101,0,0,0,0,101,0,0,0,0,0,0,101,0,0,0,101,101,0,101,0,0,0,0,0,0,0,0,0 +x86_64-sun-solaris,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101 +wasm32-wasi,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101 +x86_64-unknown-redox,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101 +aarch64-fuchsia,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101 +x86_64-fuchsia,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101 diff --git a/src/uu/relpath/Cargo.toml b/src/uu/relpath/Cargo.toml deleted file mode 100644 index 0df32efd2b7..00000000000 --- a/src/uu/relpath/Cargo.toml +++ /dev/null @@ -1,23 +0,0 @@ -[package] -name = "uu_relpath" -version = "0.0.21" -authors = ["uutils developers"] -license = "MIT" -description = "relpath ~ (uutils) display relative path of PATHNAME_TO from PATHNAME_FROM" - -homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/main/src/uu/relpath" -keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] -categories = ["command-line-utilities"] -edition = "2021" - -[lib] -path = "src/relpath.rs" - -[dependencies] -clap = { workspace = true } -uucore = { workspace = true, features = ["fs"] } - -[[bin]] -name = "relpath" -path = "src/main.rs" diff --git a/src/uu/relpath/LICENSE b/src/uu/relpath/LICENSE deleted file mode 120000 index 5853aaea53b..00000000000 --- a/src/uu/relpath/LICENSE +++ /dev/null @@ -1 +0,0 @@ -../../../LICENSE \ No newline at end of file diff --git a/src/uu/relpath/relpath.md b/src/uu/relpath/relpath.md deleted file mode 100644 index a1ff9bf4a37..00000000000 --- a/src/uu/relpath/relpath.md +++ /dev/null @@ -1,8 +0,0 @@ -# relpath - -``` -relpath [-d DIR] TO [FROM] -``` - -Convert TO destination to the relative path from the FROM dir. -If FROM path is omitted, current working dir will be used. \ No newline at end of file diff --git a/src/uu/relpath/src/main.rs b/src/uu/relpath/src/main.rs deleted file mode 100644 index b7dba76ce4d..00000000000 --- a/src/uu/relpath/src/main.rs +++ /dev/null @@ -1 +0,0 @@ -uucore::bin!(uu_relpath); diff --git a/src/uu/relpath/src/relpath.rs b/src/uu/relpath/src/relpath.rs deleted file mode 100644 index aa8b5caab96..00000000000 --- a/src/uu/relpath/src/relpath.rs +++ /dev/null @@ -1,91 +0,0 @@ -// This file is part of the uutils coreutils package. -// -// For the full copyright and license information, please view the LICENSE -// file that was distributed with this source code. - -// spell-checker:ignore (ToDO) subpath absto absfrom absbase - -use clap::{crate_version, Arg, Command}; -use std::env; -use std::path::{Path, PathBuf}; -use uucore::display::println_verbatim; -use uucore::error::{FromIo, UResult}; -use uucore::fs::{canonicalize, MissingHandling, ResolveMode}; -use uucore::{format_usage, help_about, help_usage}; - -const USAGE: &str = help_usage!("relpath.md"); -const ABOUT: &str = help_about!("relpath.md"); - -mod options { - pub const DIR: &str = "DIR"; - pub const TO: &str = "TO"; - pub const FROM: &str = "FROM"; -} - -#[uucore::main] -pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let args = args.collect_lossy(); - - let matches = uu_app().get_matches_from(args); - - let to = Path::new(matches.get_one::(options::TO).unwrap()).to_path_buf(); // required - let from = match matches.get_one::(options::FROM) { - Some(p) => Path::new(p).to_path_buf(), - None => env::current_dir().unwrap(), - }; - let absto = canonicalize(to, MissingHandling::Normal, ResolveMode::Logical) - .map_err_context(String::new)?; - let absfrom = canonicalize(from, MissingHandling::Normal, ResolveMode::Logical) - .map_err_context(String::new)?; - - if matches.contains_id(options::DIR) { - let base = Path::new(&matches.get_one::(options::DIR).unwrap()).to_path_buf(); - let absbase = canonicalize(base, MissingHandling::Normal, ResolveMode::Logical) - .map_err_context(String::new)?; - if !absto.as_path().starts_with(absbase.as_path()) - || !absfrom.as_path().starts_with(absbase.as_path()) - { - return println_verbatim(absto).map_err_context(String::new); - } - } - - let mut suffix_pos = 0; - for (f, t) in absfrom.components().zip(absto.components()) { - if f == t { - suffix_pos += 1; - } else { - break; - } - } - - let mut result = PathBuf::new(); - absfrom - .components() - .skip(suffix_pos) - .map(|_| result.push("..")) - .last(); - absto - .components() - .skip(suffix_pos) - .map(|x| result.push(x.as_os_str())) - .last(); - - println_verbatim(result).map_err_context(String::new) -} - -pub fn uu_app() -> Command { - Command::new(uucore::util_name()) - .version(crate_version!()) - .about(ABOUT) - .override_usage(format_usage(USAGE)) - .infer_long_args(true) - .arg(Arg::new(options::DIR).short('d').help( - "If any of FROM and TO is not subpath of DIR, output absolute path instead of relative", - )) - .arg( - Arg::new(options::TO) - .value_hint(clap::ValueHint::AnyPath) - .required(true), - ) - .arg(Arg::new(options::FROM).value_hint(clap::ValueHint::AnyPath)) -} diff --git a/tests/by-util/test_relpath.rs b/tests/by-util/test_relpath.rs deleted file mode 100644 index f506e01c5cc..00000000000 --- a/tests/by-util/test_relpath.rs +++ /dev/null @@ -1,189 +0,0 @@ -// This file is part of the uutils coreutils package. -// -// For the full copyright and license information, please view the LICENSE -// file that was distributed with this source code. -use crate::common::util::TestScenario; -use std::borrow::Cow; -use std::path::Path; - -struct TestCase<'a> { - from: &'a str, - to: &'a str, - expected: &'a str, -} - -const TESTS: [TestCase; 10] = [ - TestCase { - from: "A/B/C", - to: "A", - expected: "../..", - }, - TestCase { - from: "A/B/C", - to: "A/B", - expected: "..", - }, - TestCase { - from: "A/B/C", - to: "A/B/C", - expected: "", - }, - TestCase { - from: "A/B/C", - to: "A/B/C/D", - expected: "D", - }, - TestCase { - from: "A/B/C", - to: "A/B/C/D/E", - expected: "D/E", - }, - TestCase { - from: "A/B/C", - to: "A/B/D", - expected: "../D", - }, - TestCase { - from: "A/B/C", - to: "A/B/D/E", - expected: "../D/E", - }, - TestCase { - from: "A/B/C", - to: "A/D", - expected: "../../D", - }, - TestCase { - from: "A/B/C", - to: "D/E/F", - expected: "../../../D/E/F", - }, - TestCase { - from: "A/B/C", - to: "A/D/E", - expected: "../../D/E", - }, -]; - -#[allow(clippy::needless_lifetimes)] -fn convert_path<'a>(path: &'a str) -> Cow<'a, str> { - #[cfg(windows)] - return path.replace('/', "\\").into(); - #[cfg(not(windows))] - return path.into(); -} - -#[test] -fn test_relpath_with_from_no_d() { - let scene = TestScenario::new(util_name!()); - let at = &scene.fixtures; - - for test in &TESTS { - let from: &str = &convert_path(test.from); - let to: &str = &convert_path(test.to); - let expected: &str = &convert_path(test.expected); - - at.mkdir_all(to); - at.mkdir_all(from); - - scene - .ucmd() - .arg(to) - .arg(from) - .succeeds() - .stdout_only(&format!("{expected}\n")); - } -} - -#[test] -fn test_relpath_with_from_with_d() { - let scene = TestScenario::new(util_name!()); - let at = &scene.fixtures; - - for test in &TESTS { - let from: &str = &convert_path(test.from); - let to: &str = &convert_path(test.to); - let pwd = at.as_string(); - at.mkdir_all(to); - at.mkdir_all(from); - - // d is part of subpath -> expect relative path - let mut _result_stdout = scene - .ucmd() - .arg(to) - .arg(from) - .arg(&format!("-d{pwd}")) - .succeeds() - .stdout_move_str(); - // relax rules for windows test environment - #[cfg(not(windows))] - assert!(Path::new(&_result_stdout).is_relative()); - - // d is not part of subpath -> expect absolute path - _result_stdout = scene - .ucmd() - .arg(to) - .arg(from) - .arg("-dnon_existing") // spell-checker:disable-line - .succeeds() - .stdout_move_str(); - assert!(Path::new(&_result_stdout).is_absolute()); - } -} - -#[test] -fn test_relpath_no_from_no_d() { - let scene = TestScenario::new(util_name!()); - let at = &scene.fixtures; - - for test in &TESTS { - let to: &str = &convert_path(test.to); - at.mkdir_all(to); - - let _result_stdout = scene.ucmd().arg(to).succeeds().stdout_move_str(); - #[cfg(not(windows))] - assert_eq!(_result_stdout, format!("{to}\n")); - // relax rules for windows test environment - #[cfg(windows)] - assert!(_result_stdout.ends_with(&format!("{to}\n"))); - } -} - -#[test] -fn test_relpath_no_from_with_d() { - let scene = TestScenario::new(util_name!()); - let at = &scene.fixtures; - - for test in &TESTS { - let to: &str = &convert_path(test.to); - let pwd = at.as_string(); - at.mkdir_all(to); - - // d is part of subpath -> expect relative path - let _result_stdout = scene - .ucmd() - .arg(to) - .arg(&format!("-d{pwd}")) - .succeeds() - .stdout_move_str(); - // relax rules for windows test environment - #[cfg(not(windows))] - assert!(Path::new(&_result_stdout).is_relative()); - - // d is not part of subpath -> expect absolute path - let result_stdout = scene - .ucmd() - .arg(to) - .arg("-dnon_existing") // spell-checker:disable-line - .succeeds() - .stdout_move_str(); - assert!(Path::new(&result_stdout).is_absolute()); - } -} - -#[test] -fn test_relpath_no_to() { - new_ucmd!() - .fails() - .stderr_contains("required arguments were not provided"); -} diff --git a/tests/tests.rs b/tests/tests.rs index 8d4a6855c9e..1fb5735eb71 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -269,10 +269,6 @@ mod test_readlink; #[path = "by-util/test_realpath.rs"] mod test_realpath; -#[cfg(feature = "relpath")] -#[path = "by-util/test_relpath.rs"] -mod test_relpath; - #[cfg(feature = "rm")] #[path = "by-util/test_rm.rs"] mod test_rm; diff --git a/util/show-utils.BAT b/util/show-utils.BAT index 9fa5e2ffae4..f6d9007349c 100644 --- a/util/show-utils.BAT +++ b/util/show-utils.BAT @@ -2,7 +2,7 @@ @echo off @rem ::# spell-checker:ignore (CMD) ERRORLEVEL -@rem ::# spell-checker:ignore (utils) cksum coreutils dircolors hashsum mkdir mktemp printenv printf readlink realpath relpath rmdir shuf tsort unexpand +@rem ::# spell-checker:ignore (utils) cksum coreutils dircolors hashsum mkdir mktemp printenv printf readlink realpath rmdir shuf tsort unexpand @rem ::# spell-checker:ignore (jq) deps startswith set "ME=%~0" @@ -12,7 +12,7 @@ set "ME_parent_dir=%~dp0.\.." @rem refs: , @rem :: default ("Tier 1" cross-platform) utility list -set "default_utils=base32 base64 basename cat cksum comm cp cut date dircolors dirname echo env expand expr factor false fmt fold hashsum head join link ln ls mkdir mktemp more mv nl od paste printenv printf ptx pwd readlink realpath relpath rm rmdir seq shred shuf sleep sort split sum tac tail tee test tr true truncate tsort unexpand uniq wc yes" +set "default_utils=base32 base64 basename cat cksum comm cp cut date dircolors dirname echo env expand expr factor false fmt fold hashsum head join link ln ls mkdir mktemp more mv nl od paste printenv printf ptx pwd readlink realpath rm rmdir seq shred shuf sleep sort split sum tac tail tee test tr true truncate tsort unexpand uniq wc yes" set "project_dir=%ME_parent_dir%" cd "%project_dir%" diff --git a/util/show-utils.sh b/util/show-utils.sh index b6a0f9856d8..dda01abe2d2 100755 --- a/util/show-utils.sh +++ b/util/show-utils.sh @@ -1,6 +1,6 @@ #!/bin/sh -# spell-checker:ignore (utils) cksum coreutils dircolors hashsum mkdir mktemp printenv printf readlink realpath relpath rmdir shuf tsort unexpand +# spell-checker:ignore (utils) cksum coreutils dircolors hashsum mkdir mktemp printenv printf readlink realpath rmdir shuf tsort unexpand # spell-checker:ignore (jq) deps startswith ME="${0}" @@ -12,7 +12,7 @@ ME_parent_dir_abs="$(realpath -mP -- "${ME_parent_dir}" || realpath -- "${ME_par # refs: , # default ("Tier 1" cross-platform) utility list -default_utils="base32 base64 basename cat cksum comm cp cut date dircolors dirname echo env expand expr factor false fmt fold hashsum head join link ln ls mkdir mktemp more mv nl od paste printenv printf ptx pwd readlink realpath relpath rm rmdir seq shred shuf sleep sort split sum tac tail tee test tr true truncate tsort unexpand uniq wc yes" +default_utils="base32 base64 basename cat cksum comm cp cut date dircolors dirname echo env expand expr factor false fmt fold hashsum head join link ln ls mkdir mktemp more mv nl od paste printenv printf ptx pwd readlink realpath rm rmdir seq shred shuf sleep sort split sum tac tail tee test tr true truncate tsort unexpand uniq wc yes" project_main_dir="${ME_parent_dir_abs}" # printf 'project_main_dir="%s"\n' "${project_main_dir}" From b87e1f56764db61beae80970d3a7a52fee53cfa7 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Thu, 5 Oct 2023 09:31:43 +0200 Subject: [PATCH 0168/2851] chown: move uid & gid detections into their own functions --- src/uu/chown/src/chown.rs | 95 ++++++++++++++++++++------------------- 1 file changed, 49 insertions(+), 46 deletions(-) diff --git a/src/uu/chown/src/chown.rs b/src/uu/chown/src/chown.rs index 8e97d565242..ea25050bb88 100644 --- a/src/uu/chown/src/chown.rs +++ b/src/uu/chown/src/chown.rs @@ -195,6 +195,53 @@ pub fn uu_app() -> Command { ) } +/// Parses the user string to extract the UID. +fn parse_uid(user: &str, spec: &str, sep: char) -> UResult> { + if user.is_empty() { + return Ok(None); + } + match Passwd::locate(user) { + Ok(u) => Ok(Some(u.uid)), // We have been able to get the uid + Err(_) => { + // we have NOT been able to find the uid + // but we could be in the case where we have user.group + if spec.contains('.') && !spec.contains(':') && sep == ':' { + // but the input contains a '.' but not a ':' + // we might have something like username.groupname + // So, try to parse it this way + return parse_spec(spec, '.').map(|(uid, _)| uid); + } else { + // It's possible that the `user` string contains a + // numeric user ID, in which case, we respect that. + match user.parse() { + Ok(uid) => Ok(Some(uid)), + Err(_) => Err(USimpleError::new( + 1, + format!("invalid user: {}", spec.quote()), + )), + } + } + } + } +} + +/// Parses the group string to extract the GID. +fn parse_gid(group: &str, spec: &str) -> UResult> { + if group.is_empty() { + return Ok(None); + } + match Group::locate(group) { + Ok(g) => Ok(Some(g.gid)), + Err(_) => match group.parse() { + Ok(gid) => Ok(Some(gid)), + Err(_) => Err(USimpleError::new( + 1, + format!("invalid group: {}", spec.quote()), + )), + }, + } +} + /// Parse the owner/group specifier string into a user ID and a group ID. /// /// The `spec` can be of the form: @@ -213,52 +260,8 @@ fn parse_spec(spec: &str, sep: char) -> UResult<(Option, Option)> { let user = args.next().unwrap_or(""); let group = args.next().unwrap_or(""); - let uid = if user.is_empty() { - None - } else { - Some(match Passwd::locate(user) { - Ok(u) => u.uid, // We have been able to get the uid - Err(_) => - // we have NOT been able to find the uid - // but we could be in the case where we have user.group - { - if spec.contains('.') && !spec.contains(':') && sep == ':' { - // but the input contains a '.' but not a ':' - // we might have something like username.groupname - // So, try to parse it this way - return parse_spec(spec, '.'); - } else { - // It's possible that the `user` string contains a - // numeric user ID, in which case, we respect that. - match user.parse() { - Ok(uid) => uid, - Err(_) => { - return Err(USimpleError::new( - 1, - format!("invalid user: {}", spec.quote()), - )) - } - } - } - } - }) - }; - let gid = if group.is_empty() { - None - } else { - Some(match Group::locate(group) { - Ok(g) => g.gid, - Err(_) => match group.parse() { - Ok(gid) => gid, - Err(_) => { - return Err(USimpleError::new( - 1, - format!("invalid group: {}", spec.quote()), - )); - } - }, - }) - }; + let uid = parse_uid(user, spec, sep)?; + let gid = parse_gid(group, spec)?; if user.chars().next().map(char::is_numeric).unwrap_or(false) && group.is_empty() From d756a05be6f420b56a96a7d103c37227e720f23e Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Thu, 5 Oct 2023 19:18:00 +0200 Subject: [PATCH 0169/2851] touch: fix clippy warning - redundant guard --- src/uu/touch/src/touch.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/touch/src/touch.rs b/src/uu/touch/src/touch.rs index 6555773eedd..d9399a051f6 100644 --- a/src/uu/touch/src/touch.rs +++ b/src/uu/touch/src/touch.rs @@ -489,7 +489,7 @@ fn pathbuf_from_stdout() -> UResult { format!("GetFinalPathNameByHandleW failed with code {ret}"), )) } - e if e == 0 => { + 0 => { return Err(USimpleError::new( 1, format!( From f18e8983b189ed62fb381c3c3ce808281daae6d2 Mon Sep 17 00:00:00 2001 From: terade <134976752+terade@users.noreply.github.com.> Date: Wed, 4 Oct 2023 16:32:30 +0200 Subject: [PATCH 0170/2851] rm: Refactor prompt_file, lower nesting depth Addressing issue #5345. Introduce new helper function: prompt_file_permission_read_only --- src/uu/rm/src/rm.rs | 61 +++++++++++++++++++++------------------------ 1 file changed, 28 insertions(+), 33 deletions(-) diff --git a/src/uu/rm/src/rm.rs b/src/uu/rm/src/rm.rs index 87767b904bd..0f4d045985d 100644 --- a/src/uu/rm/src/rm.rs +++ b/src/uu/rm/src/rm.rs @@ -486,7 +486,6 @@ fn prompt_dir(path: &Path, options: &Options) -> bool { } } -#[allow(clippy::cognitive_complexity)] fn prompt_file(path: &Path, options: &Options) -> bool { // If interactive is Never we never want to send prompts if options.interactive == InteractiveMode::Never { @@ -503,47 +502,43 @@ fn prompt_file(path: &Path, options: &Options) -> bool { // File::open(path) doesn't open the file in write mode so we need to use file options to open it in also write mode to check if it can written too match File::options().read(true).write(true).open(path) { Ok(file) => { - if let Ok(metadata) = file.metadata() { - if metadata.permissions().readonly() { - if metadata.len() == 0 { - prompt_yes!( - "remove write-protected regular empty file {}?", - path.quote() - ) - } else { - prompt_yes!("remove write-protected regular file {}?", path.quote()) - } - } else if options.interactive == InteractiveMode::Always { - if metadata.len() == 0 { - prompt_yes!("remove regular empty file {}?", path.quote()) - } else { - prompt_yes!("remove file {}?", path.quote()) - } + let Ok(metadata) = file.metadata() else { + return true; + }; + + if options.interactive == InteractiveMode::Always && !metadata.permissions().readonly() + { + return if metadata.len() == 0 { + prompt_yes!("remove regular empty file {}?", path.quote()) } else { - true - } - } else { - true + prompt_yes!("remove file {}?", path.quote()) + }; } + prompt_file_permission_readonly(path, Ok(metadata)) } Err(err) => { - if err.kind() == ErrorKind::PermissionDenied { - match fs::metadata(path) { - Ok(metadata) if metadata.len() == 0 => { - prompt_yes!( - "remove write-protected regular empty file {}?", - path.quote() - ) - } - _ => prompt_yes!("remove write-protected regular file {}?", path.quote()), - } - } else { - true + if err.kind() != ErrorKind::PermissionDenied { + return true; } + prompt_file_permission_readonly(path, fs::metadata(path)) } } } +fn prompt_file_permission_readonly( + path: &Path, + metadata_or_err: Result, +) -> bool { + match metadata_or_err { + Ok(metadata) if !metadata.permissions().readonly() => true, + Ok(metadata) if metadata.len() == 0 => prompt_yes!( + "remove write-protected regular empty file {}?", + path.quote() + ), + _ => prompt_yes!("remove write-protected regular file {}?", path.quote()), + } +} + // For directories finding if they are writable or not is a hassle. In Unix we can use the built-in rust crate to to check mode bits. But other os don't have something similar afaik // Most cases are covered by keep eye out for edge cases #[cfg(unix)] From 7c3de4084294bef465ff38132b60884a7c40c4b3 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 6 Oct 2023 05:01:49 +0000 Subject: [PATCH 0171/2851] chore(deps): update rust crate byteorder to 1.5.0 --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 33ee7ff1477..19ea899d80e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -211,9 +211,9 @@ checksum = "ad152d03a2c813c80bb94fedbf3a3f02b28f793e39e7c214c8a0bcc196343de7" [[package]] name = "byteorder" -version = "1.4.3" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cc" diff --git a/Cargo.toml b/Cargo.toml index e7fc2851bd8..83697cf008d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -262,7 +262,7 @@ bigdecimal = "0.4" binary-heap-plus = "0.5.0" bstr = "1.6" bytecount = "0.6.4" -byteorder = "1.4.3" +byteorder = "1.5.0" chrono = { version = "^0.4.31", default-features = false, features = [ "std", "alloc", From 4760b1f340dfe30f23fdc8676211c7ae48b963da Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Fri, 6 Oct 2023 07:49:36 +0200 Subject: [PATCH 0172/2851] chown: remove unnecessary return --- src/uu/chown/src/chown.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/chown/src/chown.rs b/src/uu/chown/src/chown.rs index ea25050bb88..0e9b8b2423c 100644 --- a/src/uu/chown/src/chown.rs +++ b/src/uu/chown/src/chown.rs @@ -209,7 +209,7 @@ fn parse_uid(user: &str, spec: &str, sep: char) -> UResult> { // but the input contains a '.' but not a ':' // we might have something like username.groupname // So, try to parse it this way - return parse_spec(spec, '.').map(|(uid, _)| uid); + parse_spec(spec, '.').map(|(uid, _)| uid) } else { // It's possible that the `user` string contains a // numeric user ID, in which case, we respect that. From 6dd53c7a717daac2dcc5f71f4efcf3c4351e7154 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Fri, 6 Oct 2023 10:11:26 +0200 Subject: [PATCH 0173/2851] clippy: fix warnings in tests --- tests/by-util/test_cp.rs | 1 - tests/by-util/test_split.rs | 40 ++++++++++++++++++------------------- tests/by-util/test_tee.rs | 17 +++++++++++++--- 3 files changed, 34 insertions(+), 24 deletions(-) diff --git a/tests/by-util/test_cp.rs b/tests/by-util/test_cp.rs index 15713587c7f..9894087e8b2 100644 --- a/tests/by-util/test_cp.rs +++ b/tests/by-util/test_cp.rs @@ -1545,7 +1545,6 @@ fn test_cp_preserve_links_case_7() { .arg("dest") .fails() .stderr_contains("not replacing"); - (); assert!(at.dir_exists("dest")); assert!(at.plus("dest").join("f").exists()); diff --git a/tests/by-util/test_split.rs b/tests/by-util/test_split.rs index 6fc3a370613..ce80844cf3e 100644 --- a/tests/by-util/test_split.rs +++ b/tests/by-util/test_split.rs @@ -438,7 +438,7 @@ fn test_split_obs_lines_within_combined_shorts() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; let name = "obs-lines-within-shorts"; - RandomFile::new(&at, name).add_lines(400); + RandomFile::new(at, name).add_lines(400); scene .ucmd() @@ -446,9 +446,9 @@ fn test_split_obs_lines_within_combined_shorts() { .succeeds() .no_stderr() .no_stdout(); - let glob = Glob::new(&at, ".", r"x\d\d$"); + let glob = Glob::new(at, ".", r"x\d\d$"); assert_eq!(glob.count(), 2); - assert_eq!(glob.collate(), at.read_bytes(name)) + assert_eq!(glob.collate(), at.read_bytes(name)); } /// Test for obsolete lines option as part of combined short options with tailing suffix length with value @@ -470,7 +470,7 @@ fn test_split_obs_lines_starts_combined_shorts() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; let name = "obs-lines-starts-shorts"; - RandomFile::new(&at, name).add_lines(400); + RandomFile::new(at, name).add_lines(400); scene .ucmd() @@ -478,9 +478,9 @@ fn test_split_obs_lines_starts_combined_shorts() { .succeeds() .no_stderr() .no_stdout(); - let glob = Glob::new(&at, ".", r"x\d\d$"); + let glob = Glob::new(at, ".", r"x\d\d$"); assert_eq!(glob.count(), 2); - assert_eq!(glob.collate(), at.read_bytes(name)) + assert_eq!(glob.collate(), at.read_bytes(name)); } /// Test for using both obsolete lines (standalone) option and short/long lines option simultaneously @@ -585,7 +585,7 @@ fn test_split_multiple_obs_lines_standalone() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; let name = "multiple-obs-lines"; - RandomFile::new(&at, name).add_lines(400); + RandomFile::new(at, name).add_lines(400); scene .ucmd() @@ -593,9 +593,9 @@ fn test_split_multiple_obs_lines_standalone() { .succeeds() .no_stderr() .no_stdout(); - let glob = Glob::new(&at, ".", r"x[[:alpha:]][[:alpha:]]$"); + let glob = Glob::new(at, ".", r"x[[:alpha:]][[:alpha:]]$"); assert_eq!(glob.count(), 2); - assert_eq!(glob.collate(), at.read_bytes(name)) + assert_eq!(glob.collate(), at.read_bytes(name)); } /// Test for using more than one obsolete lines option within combined shorts @@ -605,7 +605,7 @@ fn test_split_multiple_obs_lines_within_combined() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; let name = "multiple-obs-lines"; - RandomFile::new(&at, name).add_lines(400); + RandomFile::new(at, name).add_lines(400); scene .ucmd() @@ -613,9 +613,9 @@ fn test_split_multiple_obs_lines_within_combined() { .succeeds() .no_stderr() .no_stdout(); - let glob = Glob::new(&at, ".", r"x\d\d$"); + let glob = Glob::new(at, ".", r"x\d\d$"); assert_eq!(glob.count(), 2); - assert_eq!(glob.collate(), at.read_bytes(name)) + assert_eq!(glob.collate(), at.read_bytes(name)); } /// Test for using both obsolete lines option within combined shorts with conflicting -n option simultaneously @@ -1543,8 +1543,8 @@ fn test_split_separator_nul_lines() { ucmd.args(&["--lines=2", "-t", "\\0", "separator_nul.txt"]) .succeeds(); - assert_eq!(file_read(&at, "xaa"), "1\02\0"); - assert_eq!(file_read(&at, "xab"), "3\04\0"); + assert_eq!(file_read(&at, "xaa"), "1\x002\0"); + assert_eq!(file_read(&at, "xab"), "3\x004\0"); assert_eq!(file_read(&at, "xac"), "5\0"); assert!(!at.plus("xad").exists()); } @@ -1555,8 +1555,8 @@ fn test_split_separator_nul_line_bytes() { ucmd.args(&["--line-bytes=4", "-t", "\\0", "separator_nul.txt"]) .succeeds(); - assert_eq!(file_read(&at, "xaa"), "1\02\0"); - assert_eq!(file_read(&at, "xab"), "3\04\0"); + assert_eq!(file_read(&at, "xaa"), "1\x002\0"); + assert_eq!(file_read(&at, "xab"), "3\x004\0"); assert_eq!(file_read(&at, "xac"), "5\0"); assert!(!at.plus("xad").exists()); } @@ -1567,8 +1567,8 @@ fn test_split_separator_nul_number_l() { ucmd.args(&["--number=l/3", "--separator=\\0", "separator_nul.txt"]) .succeeds(); - assert_eq!(file_read(&at, "xaa"), "1\02\0"); - assert_eq!(file_read(&at, "xab"), "3\04\0"); + assert_eq!(file_read(&at, "xaa"), "1\x002\0"); + assert_eq!(file_read(&at, "xab"), "3\x004\0"); assert_eq!(file_read(&at, "xac"), "5\0"); assert!(!at.plus("xad").exists()); } @@ -1579,8 +1579,8 @@ fn test_split_separator_nul_number_r() { ucmd.args(&["--number=r/3", "--separator=\\0", "separator_nul.txt"]) .succeeds(); - assert_eq!(file_read(&at, "xaa"), "1\04\0"); - assert_eq!(file_read(&at, "xab"), "2\05\0"); + assert_eq!(file_read(&at, "xaa"), "1\x004\0"); + assert_eq!(file_read(&at, "xab"), "2\x005\0"); assert_eq!(file_read(&at, "xac"), "3\0"); assert!(!at.plus("xad").exists()); } diff --git a/tests/by-util/test_tee.rs b/tests/by-util/test_tee.rs index 6f04edfc37a..2b3fd2670ab 100644 --- a/tests/by-util/test_tee.rs +++ b/tests/by-util/test_tee.rs @@ -3,6 +3,7 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. use crate::common::util::TestScenario; +use std::fmt::Write; // tests for basic tee functionality. // inspired by: @@ -74,7 +75,10 @@ fn test_tee_append() { fn test_tee_no_more_writeable_1() { // equals to 'tee /dev/full out2 (); + let content = (1..=10).fold(String::new(), |mut output, x| { + let _ = writeln!(output, "{x}"); + output + }); let file_out = "tee_file_out"; ucmd.arg("/dev/full") @@ -94,7 +98,10 @@ fn test_tee_no_more_writeable_2() { // but currently there is no way to redirect stdout to /dev/full // so this test is disabled let (_at, mut ucmd) = at_and_ucmd!(); - let _content = (1..=10).map(|x| format!("{x}\n")).collect::(); + let _content = (1..=10).fold(String::new(), |mut output, x| { + let _ = writeln!(output, "{x}"); + output + }); let file_out_a = "tee_file_out_a"; let file_out_b = "tee_file_out_b"; @@ -114,6 +121,7 @@ fn test_tee_no_more_writeable_2() { mod linux_only { use crate::common::util::{AtPath, TestScenario, UCommand}; + use std::fmt::Write; use std::fs::File; use std::process::{Output, Stdio}; @@ -135,7 +143,10 @@ mod linux_only { } fn run_tee(proc: &mut UCommand) -> (String, Output) { - let content = (1..=100_000).map(|x| format!("{x}\n")).collect::(); + let content = (1..=100_000).fold(String::new(), |mut output, x| { + let _ = writeln!(output, "{x}"); + output + }); #[allow(deprecated)] let output = proc From fa2a14ccd262f102ca04e47529d5f79a556a1692 Mon Sep 17 00:00:00 2001 From: Zhuoxun Yang Date: Fri, 6 Oct 2023 22:55:30 +0800 Subject: [PATCH 0174/2851] expr: unify debug message --- src/uu/expr/src/syntax_tree.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/expr/src/syntax_tree.rs b/src/uu/expr/src/syntax_tree.rs index b3cd329ba6e..335405d06b2 100644 --- a/src/uu/expr/src/syntax_tree.rs +++ b/src/uu/expr/src/syntax_tree.rs @@ -52,7 +52,7 @@ impl AstNode { operands, } => { println!( - "Node( {} ) at #{} (evaluate -> {:?})", + "Node( {} ) at #{} ( evaluate -> {:?} )", op_type, token_idx, self.evaluate() From 6091bafe08178f88f57aed9d28a1e1dda659bfd4 Mon Sep 17 00:00:00 2001 From: PThorpe92 Date: Mon, 25 Sep 2023 18:24:20 -0400 Subject: [PATCH 0175/2851] feat(mv): expose functionality, document for nushell --- src/uu/mv/src/mv.rs | 171 +++++++++++++++++++++++++++++--------------- 1 file changed, 113 insertions(+), 58 deletions(-) diff --git a/src/uu/mv/src/mv.rs b/src/uu/mv/src/mv.rs index 43f8eb6b651..267179bbf1b 100644 --- a/src/uu/mv/src/mv.rs +++ b/src/uu/mv/src/mv.rs @@ -3,7 +3,7 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// spell-checker:ignore (ToDO) sourcepath targetpath +// spell-checker:ignore (ToDO) sourcepath targetpath nushell mod error; @@ -23,6 +23,7 @@ use uucore::backup_control::{self, source_is_target_backup, BackupMode}; use uucore::display::Quotable; use uucore::error::{set_exit_code, FromIo, UError, UResult, USimpleError, UUsageError}; use uucore::fs::{are_hardlinks_or_one_way_symlink_to_same_file, are_hardlinks_to_same_file}; +use uucore::libc::ENOTEMPTY; use uucore::update_control::{self, UpdateMode}; use uucore::{format_usage, help_about, help_section, help_usage, prompt_yes, show}; @@ -32,23 +33,56 @@ use fs_extra::dir::{ }; use crate::error::MvError; - -pub struct Behavior { - overwrite: OverwriteMode, - backup: BackupMode, - suffix: String, - update: UpdateMode, - target_dir: Option, - no_target_dir: bool, - verbose: bool, - strip_slashes: bool, - progress_bar: bool, +/// Options contains all the possible behaviors and flags for mv. +/// +/// All options are public so that the options can be programmatically +/// constructed by other crates, such as nushell. That means that this struct +/// is part of our public API. It should therefore not be changed without good +/// reason. +/// The fields are documented with the arguments that determine their value. +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct Options { + /// specifies overwrite behavior + /// '-n' '--no-clobber' + /// '-i' '--interactive' + /// '-f' '--force' + pub overwrite: OverwriteMode, + + /// `--backup[=CONTROL]`, `-b` + pub backup: BackupMode, + + /// '-S' --suffix' backup suffix + pub suffix: String, + + /// Available update mode "--update-mode=all|none|older" + pub update: UpdateMode, + + /// Specifies target directory + /// '-t, --target-directory=DIRECTORY' + pub target_dir: Option, + + /// Treat destination as a normal file + /// '-T, --no-target-directory + pub no_target_dir: bool, + + /// '-v, --verbose' + pub verbose: bool, + + /// '--strip-trailing-slashes' + pub strip_slashes: bool, + + /// '-g, --progress' + pub progress_bar: bool, } -#[derive(Clone, Eq, PartialEq)] +/// specifies behavior of the overwrite flag +#[derive(Clone, Debug, Eq, PartialEq)] pub enum OverwriteMode { + /// '-n' '--no-clobber' do not overwrite NoClobber, + /// '-i' '--interactive' prompt before overwrite Interactive, + ///'-f' '--force' overwrite without prompt Force, } @@ -116,7 +150,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } } - let behavior = Behavior { + let opts = Options { overwrite: overwrite_mode, backup: backup_mode, suffix: backup_suffix, @@ -128,7 +162,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { progress_bar: matches.get_flag(OPT_PROGRESS), }; - exec(&files[..], &behavior) + exec_mv(&files[..], &opts) } pub fn uu_app() -> Command { @@ -220,7 +254,7 @@ pub fn uu_app() -> Command { ) } -fn determine_overwrite_mode(matches: &ArgMatches) -> OverwriteMode { +pub fn determine_overwrite_mode(matches: &ArgMatches) -> OverwriteMode { // This does not exactly match the GNU implementation: // The GNU mv defaults to Force, but if more than one of the // overwrite options are supplied, only the last takes effect. @@ -235,10 +269,10 @@ fn determine_overwrite_mode(matches: &ArgMatches) -> OverwriteMode { } } -fn parse_paths(files: &[OsString], b: &Behavior) -> Vec { +fn parse_paths(files: &[OsString], opts: &Options) -> Vec { let paths = files.iter().map(Path::new); - if b.strip_slashes { + if opts.strip_slashes { paths .map(|p| p.components().as_path().to_owned()) .collect::>() @@ -247,8 +281,10 @@ fn parse_paths(files: &[OsString], b: &Behavior) -> Vec { } } -fn handle_two_paths(source: &Path, target: &Path, b: &Behavior) -> UResult<()> { - if b.backup == BackupMode::SimpleBackup && source_is_target_backup(source, target, &b.suffix) { +fn handle_two_paths(source: &Path, target: &Path, opts: &Options) -> UResult<()> { + if opts.backup == BackupMode::SimpleBackup + && source_is_target_backup(source, target, &opts.suffix) + { return Err(io::Error::new( io::ErrorKind::NotFound, format!( @@ -266,7 +302,7 @@ fn handle_two_paths(source: &Path, target: &Path, b: &Behavior) -> UResult<()> { if (source.eq(target) || are_hardlinks_to_same_file(source, target) || are_hardlinks_or_one_way_symlink_to_same_file(source, target)) - && b.backup == BackupMode::NoBackup + && opts.backup == BackupMode::NoBackup { if source.eq(Path::new(".")) || source.ends_with("/.") || source.is_file() { return Err( @@ -278,19 +314,19 @@ fn handle_two_paths(source: &Path, target: &Path, b: &Behavior) -> UResult<()> { } if target.is_dir() { - if b.no_target_dir { + if opts.no_target_dir { if source.is_dir() { - rename(source, target, b, None).map_err_context(|| { + rename(source, target, opts, None).map_err_context(|| { format!("cannot move {} to {}", source.quote(), target.quote()) }) } else { Err(MvError::DirectoryToNonDirectory(target.quote().to_string()).into()) } } else { - move_files_into_dir(&[source.to_path_buf()], target, b) + move_files_into_dir(&[source.to_path_buf()], target, opts) } } else if target.exists() && source.is_dir() { - match b.overwrite { + match opts.overwrite { OverwriteMode::NoClobber => return Ok(()), OverwriteMode::Interactive => { if !prompt_yes!("overwrite {}? ", target.quote()) { @@ -305,12 +341,12 @@ fn handle_two_paths(source: &Path, target: &Path, b: &Behavior) -> UResult<()> { ) .into()) } else { - rename(source, target, b, None).map_err(|e| USimpleError::new(1, format!("{e}"))) + rename(source, target, opts, None).map_err(|e| USimpleError::new(1, format!("{e}"))) } } -fn handle_multiple_paths(paths: &[PathBuf], b: &Behavior) -> UResult<()> { - if b.no_target_dir { +fn handle_multiple_paths(paths: &[PathBuf], opts: &Options) -> UResult<()> { + if opts.no_target_dir { return Err(UUsageError::new( 1, format!("mv: extra operand {}", paths[2].quote()), @@ -319,24 +355,29 @@ fn handle_multiple_paths(paths: &[PathBuf], b: &Behavior) -> UResult<()> { let target_dir = paths.last().unwrap(); let sources = &paths[..paths.len() - 1]; - move_files_into_dir(sources, target_dir, b) + move_files_into_dir(sources, target_dir, opts) } -fn exec(files: &[OsString], b: &Behavior) -> UResult<()> { - let paths = parse_paths(files, b); +/// Execute mv command, moving 'source' to 'target', where +/// 'target' is a directory. If 'target' does not exist, and source is a single +/// file or directory, then 'source' will be renamed to 'target'. +/// +/// returns MvError | UError +pub fn exec_mv(files: &[OsString], opts: &Options) -> UResult<()> { + let paths = parse_paths(files, opts); - if let Some(ref name) = b.target_dir { - return move_files_into_dir(&paths, &PathBuf::from(name), b); + if let Some(ref name) = opts.target_dir { + return move_files_into_dir(&paths, &PathBuf::from(name), opts); } match paths.len() { - 2 => handle_two_paths(&paths[0], &paths[1], b), - _ => handle_multiple_paths(&paths, b), + 2 => handle_two_paths(&paths[0], &paths[1], opts), + _ => handle_multiple_paths(&paths, opts), } } #[allow(clippy::cognitive_complexity)] -fn move_files_into_dir(files: &[PathBuf], target_dir: &Path, b: &Behavior) -> UResult<()> { +fn move_files_into_dir(files: &[PathBuf], target_dir: &Path, opts: &Options) -> UResult<()> { if !target_dir.is_dir() { return Err(MvError::NotADirectory(target_dir.quote().to_string()).into()); } @@ -345,7 +386,7 @@ fn move_files_into_dir(files: &[PathBuf], target_dir: &Path, b: &Behavior) -> UR .canonicalize() .unwrap_or_else(|_| target_dir.to_path_buf()); - let multi_progress = b.progress_bar.then(MultiProgress::new); + let multi_progress = opts.progress_bar.then(MultiProgress::new); let count_progress = if let Some(ref multi_progress) = multi_progress { if files.len() > 1 { @@ -396,24 +437,37 @@ fn move_files_into_dir(files: &[PathBuf], target_dir: &Path, b: &Behavior) -> UR } } - match rename(sourcepath, &targetpath, b, multi_progress.as_ref()) { + match rename(sourcepath, &targetpath, opts, multi_progress.as_ref()) { Err(e) if e.to_string().is_empty() => set_exit_code(1), Err(e) => { - let e = e.map_err_context(|| { - format!( - "cannot move {} to {}", - sourcepath.quote(), - targetpath.quote() - ) - }); - match multi_progress { - Some(ref pb) => pb.suspend(|| show!(e)), - None => show!(e), - }; + match e.raw_os_error() { + Some(ENOTEMPTY) => { + // The error message was changed to match GNU's decision + // when an issue was filed. These will match when merged upstream. + let e = e + .map_err_context(|| format!("cannot overwrite {}", targetpath.quote())); + match multi_progress { + Some(ref pb) => pb.suspend(|| show!(e)), + None => show!(e), + }; + } + _ => { + let e = e.map_err_context(|| { + format!( + "cannot move {} to {}", + sourcepath.quote(), + targetpath.quote() + ) + }); + match multi_progress { + Some(ref pb) => pb.suspend(|| show!(e)), + None => show!(e), + }; + } + } } Ok(()) => (), } - if let Some(ref pb) = count_progress { pb.inc(1); } @@ -424,29 +478,30 @@ fn move_files_into_dir(files: &[PathBuf], target_dir: &Path, b: &Behavior) -> UR fn rename( from: &Path, to: &Path, - b: &Behavior, + opts: &Options, multi_progress: Option<&MultiProgress>, ) -> io::Result<()> { let mut backup_path = None; if to.exists() { - if b.update == UpdateMode::ReplaceIfOlder && b.overwrite == OverwriteMode::Interactive { + if opts.update == UpdateMode::ReplaceIfOlder && opts.overwrite == OverwriteMode::Interactive + { // `mv -i --update old new` when `new` exists doesn't move anything // and exit with 0 return Ok(()); } - if b.update == UpdateMode::ReplaceNone { + if opts.update == UpdateMode::ReplaceNone { return Ok(()); } - if (b.update == UpdateMode::ReplaceIfOlder) + if (opts.update == UpdateMode::ReplaceIfOlder) && fs::metadata(from)?.modified()? <= fs::metadata(to)?.modified()? { return Ok(()); } - match b.overwrite { + match opts.overwrite { OverwriteMode::NoClobber => { let err_msg = format!("not replacing {}", to.quote()); return Err(io::Error::new(io::ErrorKind::Other, err_msg)); @@ -459,7 +514,7 @@ fn rename( OverwriteMode::Force => {} }; - backup_path = backup_control::get_backup_path(b.backup, to, &b.suffix); + backup_path = backup_control::get_backup_path(opts.backup, to, &opts.suffix); if let Some(ref backup_path) = backup_path { rename_with_fallback(to, backup_path, multi_progress)?; } @@ -472,14 +527,14 @@ fn rename( if is_empty_dir(to) { fs::remove_dir(to)?; } else { - return Err(io::Error::new(io::ErrorKind::Other, "Directory not empty")); + return Err(io::Error::from_raw_os_error(ENOTEMPTY)); } } } rename_with_fallback(from, to, multi_progress)?; - if b.verbose { + if opts.verbose { let message = match backup_path { Some(path) => format!( "renamed {} -> {} (backup: {})", From 4a44a106a0442cf53f02efb2fc317a9b695125e9 Mon Sep 17 00:00:00 2001 From: PThorpe92 Date: Thu, 28 Sep 2023 15:11:48 -0400 Subject: [PATCH 0176/2851] feat: expose mv externals + document for nushell --- src/uu/mv/src/mv.rs | 59 +++++++++++++++++---------------------------- 1 file changed, 22 insertions(+), 37 deletions(-) diff --git a/src/uu/mv/src/mv.rs b/src/uu/mv/src/mv.rs index 267179bbf1b..9888389aefe 100644 --- a/src/uu/mv/src/mv.rs +++ b/src/uu/mv/src/mv.rs @@ -19,11 +19,11 @@ use std::os::unix; #[cfg(windows)] use std::os::windows; use std::path::{Path, PathBuf}; -use uucore::backup_control::{self, source_is_target_backup, BackupMode}; +pub use uucore::backup_control::BackupMode; +use uucore::backup_control::{self, source_is_target_backup}; use uucore::display::Quotable; use uucore::error::{set_exit_code, FromIo, UError, UResult, USimpleError, UUsageError}; use uucore::fs::{are_hardlinks_or_one_way_symlink_to_same_file, are_hardlinks_to_same_file}; -use uucore::libc::ENOTEMPTY; use uucore::update_control::{self, UpdateMode}; use uucore::{format_usage, help_about, help_section, help_usage, prompt_yes, show}; @@ -33,12 +33,13 @@ use fs_extra::dir::{ }; use crate::error::MvError; + /// Options contains all the possible behaviors and flags for mv. /// /// All options are public so that the options can be programmatically -/// constructed by other crates, such as nushell. That means that this struct -/// is part of our public API. It should therefore not be changed without good -/// reason. +/// constructed by other crates, such as nushell. That means that this struct is +/// part of our public API. It should therefore not be changed without good reason. +/// /// The fields are documented with the arguments that determine their value. #[derive(Debug, Clone, Eq, PartialEq)] pub struct Options { @@ -162,7 +163,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { progress_bar: matches.get_flag(OPT_PROGRESS), }; - exec_mv(&files[..], &opts) + mv(&files[..], &opts) } pub fn uu_app() -> Command { @@ -254,7 +255,7 @@ pub fn uu_app() -> Command { ) } -pub fn determine_overwrite_mode(matches: &ArgMatches) -> OverwriteMode { +fn determine_overwrite_mode(matches: &ArgMatches) -> OverwriteMode { // This does not exactly match the GNU implementation: // The GNU mv defaults to Force, but if more than one of the // overwrite options are supplied, only the last takes effect. @@ -358,12 +359,10 @@ fn handle_multiple_paths(paths: &[PathBuf], opts: &Options) -> UResult<()> { move_files_into_dir(sources, target_dir, opts) } -/// Execute mv command, moving 'source' to 'target', where +/// Execute the mv command. This moves 'source' to 'target', where /// 'target' is a directory. If 'target' does not exist, and source is a single /// file or directory, then 'source' will be renamed to 'target'. -/// -/// returns MvError | UError -pub fn exec_mv(files: &[OsString], opts: &Options) -> UResult<()> { +pub fn mv(files: &[OsString], opts: &Options) -> UResult<()> { let paths = parse_paths(files, opts); if let Some(ref name) = opts.target_dir { @@ -440,31 +439,17 @@ fn move_files_into_dir(files: &[PathBuf], target_dir: &Path, opts: &Options) -> match rename(sourcepath, &targetpath, opts, multi_progress.as_ref()) { Err(e) if e.to_string().is_empty() => set_exit_code(1), Err(e) => { - match e.raw_os_error() { - Some(ENOTEMPTY) => { - // The error message was changed to match GNU's decision - // when an issue was filed. These will match when merged upstream. - let e = e - .map_err_context(|| format!("cannot overwrite {}", targetpath.quote())); - match multi_progress { - Some(ref pb) => pb.suspend(|| show!(e)), - None => show!(e), - }; - } - _ => { - let e = e.map_err_context(|| { - format!( - "cannot move {} to {}", - sourcepath.quote(), - targetpath.quote() - ) - }); - match multi_progress { - Some(ref pb) => pb.suspend(|| show!(e)), - None => show!(e), - }; - } - } + let e = e.map_err_context(|| { + format!( + "cannot move {} to {}", + sourcepath.quote(), + targetpath.quote() + ) + }); + match multi_progress { + Some(ref pb) => pb.suspend(|| show!(e)), + None => show!(e), + }; } Ok(()) => (), } @@ -527,7 +512,7 @@ fn rename( if is_empty_dir(to) { fs::remove_dir(to)?; } else { - return Err(io::Error::from_raw_os_error(ENOTEMPTY)); + return Err(io::Error::new(io::ErrorKind::Other, "Directory not empty")); } } } From 2c2e01205cfbe4b6337a207102485fd2dc64e443 Mon Sep 17 00:00:00 2001 From: Zhuoxun Yang Date: Fri, 6 Oct 2023 23:50:22 +0800 Subject: [PATCH 0177/2851] expr: short-circuit evaluation for | --- src/uu/expr/src/syntax_tree.rs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/uu/expr/src/syntax_tree.rs b/src/uu/expr/src/syntax_tree.rs index 335405d06b2..ad421edb7f3 100644 --- a/src/uu/expr/src/syntax_tree.rs +++ b/src/uu/expr/src/syntax_tree.rs @@ -155,8 +155,24 @@ impl AstNode { } } pub fn operand_values(&self) -> Result, String> { - if let Self::Node { operands, .. } = self { + if let Self::Node { + operands, op_type, .. + } = self + { let mut out = Vec::with_capacity(operands.len()); + let mut operands = operands.iter(); + + if op_type == "|" { + if let Some(value) = operands.next() { + let value = value.evaluate()?; + out.push(value.clone()); + if value_as_bool(&value) { + out.push(String::from("dummy")); + return Ok(out); + } + } + } + for operand in operands { let value = operand.evaluate()?; out.push(value); From 5a732dd21a9aa0987f973eda4ffe919b9985be29 Mon Sep 17 00:00:00 2001 From: Zhuoxun Yang Date: Fri, 6 Oct 2023 23:50:44 +0800 Subject: [PATCH 0178/2851] tests/expr: add test expr 1 \| a / 5 --- tests/by-util/test_expr.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/by-util/test_expr.rs b/tests/by-util/test_expr.rs index ea5a964d999..99445d56641 100644 --- a/tests/by-util/test_expr.rs +++ b/tests/by-util/test_expr.rs @@ -123,6 +123,11 @@ fn test_or() { .args(&["-14", "|", "1"]) .succeeds() .stdout_only("-14\n"); + + new_ucmd!() + .args(&["1", "|", "a", "/", "5"]) + .succeeds() + .stdout_only("1\n"); } #[test] From 0df561e256905c9477bd316b68a9fb56d36a47c6 Mon Sep 17 00:00:00 2001 From: Luv_Ray Date: Sat, 7 Oct 2023 00:19:56 +0800 Subject: [PATCH 0179/2851] expr: add comment in syntax_tree.rs --- src/uu/expr/src/syntax_tree.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/uu/expr/src/syntax_tree.rs b/src/uu/expr/src/syntax_tree.rs index ad421edb7f3..eaa6be1f51e 100644 --- a/src/uu/expr/src/syntax_tree.rs +++ b/src/uu/expr/src/syntax_tree.rs @@ -161,7 +161,8 @@ impl AstNode { { let mut out = Vec::with_capacity(operands.len()); let mut operands = operands.iter(); - + // check the first value before `|`, stop evaluate and return directly if it is true. + // push dummy to pass the check of `len() == 2` if op_type == "|" { if let Some(value) = operands.next() { let value = value.evaluate()?; From 7bf4b7f6748812e29daf3fb91180e4dfceb6ce30 Mon Sep 17 00:00:00 2001 From: Luv_Ray Date: Sat, 7 Oct 2023 00:42:04 +0800 Subject: [PATCH 0180/2851] tests/expr: add tests in test_expr.rs --- tests/by-util/test_expr.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/by-util/test_expr.rs b/tests/by-util/test_expr.rs index 99445d56641..a463f086b72 100644 --- a/tests/by-util/test_expr.rs +++ b/tests/by-util/test_expr.rs @@ -128,6 +128,16 @@ fn test_or() { .args(&["1", "|", "a", "/", "5"]) .succeeds() .stdout_only("1\n"); + + new_ucmd!() + .args(&["foo", "|", "a", "/", "5"]) + .succeeds() + .stdout_only("foo\n"); + + new_ucmd!() + .args(&["0", "|", "10", "/", "5"]) + .succeeds() + .stdout_only("2\n"); } #[test] From 98cfb0c322b33d571647ba7c0ac6831624266eae Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Fri, 6 Oct 2023 22:08:04 +0200 Subject: [PATCH 0181/2851] Binary sizes: handle when 0 (relpath removal) --- .github/workflows/CICD.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index a9f04e3cf1d..61c12dad0cd 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -610,6 +610,12 @@ jobs: check() { # Warn if the size increases by more than 5% threshold='1.05' + + if [[ "$2" -eq 0 || "$3" -eq 0 ]]; then + echo "::warning file=$4::Invalid size for $1. Sizes cannot be 0." + return + fi + ratio=$(jq -n "$2 / $3") echo "$1: size=$2, previous_size=$3, ratio=$ratio, threshold=$threshold" if [[ "$(jq -n "$ratio > $threshold")" == 'true' ]]; then From e5d70d444ae7ca3a3100190ea87c22edfecab121 Mon Sep 17 00:00:00 2001 From: Zhuoxun Yang Date: Sat, 7 Oct 2023 10:41:10 +0800 Subject: [PATCH 0182/2851] tests/expr: format --- tests/by-util/test_expr.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/by-util/test_expr.rs b/tests/by-util/test_expr.rs index a463f086b72..4637d51c739 100644 --- a/tests/by-util/test_expr.rs +++ b/tests/by-util/test_expr.rs @@ -128,7 +128,7 @@ fn test_or() { .args(&["1", "|", "a", "/", "5"]) .succeeds() .stdout_only("1\n"); - + new_ucmd!() .args(&["foo", "|", "a", "/", "5"]) .succeeds() From 959bfa0160165b8ed23f1db4cd9ea43264f3aadd Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 7 Oct 2023 02:43:46 +0000 Subject: [PATCH 0183/2851] chore(deps): update rust crate libc to 0.2.149 --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a2c872e2d93..a3c3e709627 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1200,9 +1200,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.148" +version = "0.2.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b" +checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" [[package]] name = "libloading" diff --git a/Cargo.toml b/Cargo.toml index 783c84ccf0b..e59ed7bd7b1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -286,7 +286,7 @@ glob = "0.3.1" half = "2.3" indicatif = "0.17" itertools = "0.11.0" -libc = "0.2.148" +libc = "0.2.149" lscolors = { version = "0.15.0", default-features = false, features = [ "nu-ansi-term", ] } From e1b7f254a63a00b1b33a8842ca6280b183df8eb6 Mon Sep 17 00:00:00 2001 From: Sanpi Date: Sat, 7 Oct 2023 10:39:19 +0200 Subject: [PATCH 0184/2851] ls: fix panic when file removed too quickly Fixes #5371 --- src/uu/ls/src/ls.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 301aefaf3a3..c80d4cbe641 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -2888,7 +2888,11 @@ fn classify_file(path: &PathData, out: &mut BufWriter) -> Option { Some('=') } else if file_type.is_fifo() { Some('|') - } else if file_type.is_file() && file_is_executable(path.md(out).as_ref().unwrap()) { + } else if file_type.is_file() + // Safe unwrapping if the file was removed between listing and display + // See https://github.com/uutils/coreutils/issues/5371 + && path.md(out).map(file_is_executable).unwrap_or_default() + { Some('*') } else { None From 3e1d3cacebe447dfa4efa8d5b722abc9ad0bca3d Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sat, 7 Oct 2023 13:37:24 +0200 Subject: [PATCH 0185/2851] head: remove clippy::cognitive_complexity by moving some content in a function (#5366) * head: remove a clippy::cognitive_complexity by moving some content into a function * Remove duplicate comment --- src/uu/head/src/parse.rs | 129 +++++++++++++++++++++------------------ 1 file changed, 69 insertions(+), 60 deletions(-) diff --git a/src/uu/head/src/parse.rs b/src/uu/head/src/parse.rs index 90e1f2ce008..179435c7ae5 100644 --- a/src/uu/head/src/parse.rs +++ b/src/uu/head/src/parse.rs @@ -13,7 +13,6 @@ pub enum ParseError { } /// Parses obsolete syntax /// head -NUM\[kmzv\] // spell-checker:disable-line -#[allow(clippy::cognitive_complexity)] pub fn parse_obsolete(src: &str) -> Option, ParseError>> { let mut chars = src.char_indices(); if let Some((_, '-')) = chars.next() { @@ -30,65 +29,7 @@ pub fn parse_obsolete(src: &str) -> Option } } if has_num { - match src[1..=num_end].parse::() { - Ok(num) => { - let mut quiet = false; - let mut verbose = false; - let mut zero_terminated = false; - let mut multiplier = None; - let mut c = last_char; - loop { - // not that here, we only match lower case 'k', 'c', and 'm' - match c { - // we want to preserve order - // this also saves us 1 heap allocation - 'q' => { - quiet = true; - verbose = false; - } - 'v' => { - verbose = true; - quiet = false; - } - 'z' => zero_terminated = true, - 'c' => multiplier = Some(1), - 'b' => multiplier = Some(512), - 'k' => multiplier = Some(1024), - 'm' => multiplier = Some(1024 * 1024), - '\0' => {} - _ => return Some(Err(ParseError::Syntax)), - } - if let Some((_, next)) = chars.next() { - c = next; - } else { - break; - } - } - let mut options = Vec::new(); - if quiet { - options.push(OsString::from("-q")); - } - if verbose { - options.push(OsString::from("-v")); - } - if zero_terminated { - options.push(OsString::from("-z")); - } - if let Some(n) = multiplier { - options.push(OsString::from("-c")); - let num = match num.checked_mul(n) { - Some(n) => n, - None => return Some(Err(ParseError::Overflow)), - }; - options.push(OsString::from(format!("{num}"))); - } else { - options.push(OsString::from("-n")); - options.push(OsString::from(format!("{num}"))); - } - Some(Ok(options.into_iter())) - } - Err(_) => Some(Err(ParseError::Overflow)), - } + process_num_block(&src[1..=num_end], last_char, &mut chars) } else { None } @@ -96,6 +37,74 @@ pub fn parse_obsolete(src: &str) -> Option None } } + +/// Processes the numeric block of the input string to generate the appropriate options. +fn process_num_block( + src: &str, + last_char: char, + chars: &mut std::str::CharIndices, +) -> Option, ParseError>> { + match src.parse::() { + Ok(num) => { + let mut quiet = false; + let mut verbose = false; + let mut zero_terminated = false; + let mut multiplier = None; + let mut c = last_char; + loop { + // note that here, we only match lower case 'k', 'c', and 'm' + match c { + // we want to preserve order + // this also saves us 1 heap allocation + 'q' => { + quiet = true; + verbose = false; + } + 'v' => { + verbose = true; + quiet = false; + } + 'z' => zero_terminated = true, + 'c' => multiplier = Some(1), + 'b' => multiplier = Some(512), + 'k' => multiplier = Some(1024), + 'm' => multiplier = Some(1024 * 1024), + '\0' => {} + _ => return Some(Err(ParseError::Syntax)), + } + if let Some((_, next)) = chars.next() { + c = next; + } else { + break; + } + } + let mut options = Vec::new(); + if quiet { + options.push(OsString::from("-q")); + } + if verbose { + options.push(OsString::from("-v")); + } + if zero_terminated { + options.push(OsString::from("-z")); + } + if let Some(n) = multiplier { + options.push(OsString::from("-c")); + let num = match num.checked_mul(n) { + Some(n) => n, + None => return Some(Err(ParseError::Overflow)), + }; + options.push(OsString::from(format!("{num}"))); + } else { + options.push(OsString::from("-n")); + options.push(OsString::from(format!("{num}"))); + } + Some(Ok(options.into_iter())) + } + Err(_) => Some(Err(ParseError::Overflow)), + } +} + /// Parses an -c or -n argument, /// the bool specifies whether to read from the end pub fn parse_num(src: &str) -> Result<(u64, bool), ParseSizeError> { From 2ba7400d05ec32320acdd52a395546c9ee0f2327 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Sat, 7 Oct 2023 15:15:44 +0200 Subject: [PATCH 0186/2851] clippy: suppress cognitive_complexity lint for two functions --- src/uu/ls/src/ls.rs | 1 + src/uu/split/src/split.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 301aefaf3a3..9dec8749d1b 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -1856,6 +1856,7 @@ impl PathData { } } +#[allow(clippy::cognitive_complexity)] pub fn list(locs: Vec<&Path>, config: &Config) -> UResult<()> { let mut files = Vec::::new(); let mut dirs = Vec::::new(); diff --git a/src/uu/split/src/split.rs b/src/uu/split/src/split.rs index 75624853953..bfd595e4f86 100644 --- a/src/uu/split/src/split.rs +++ b/src/uu/split/src/split.rs @@ -1714,6 +1714,7 @@ where Ok(()) } +#[allow(clippy::cognitive_complexity)] fn split(settings: &Settings) -> UResult<()> { let mut reader = BufReader::new(if settings.input == "-" { Box::new(stdin()) as Box From f86469f6d5c6d0903162aa04cedf872fc0520d67 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Sat, 7 Oct 2023 14:11:15 +0200 Subject: [PATCH 0187/2851] head: add some empty lines --- src/uu/head/src/head.rs | 13 ++++++++++++- src/uu/head/src/parse.rs | 8 ++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/uu/head/src/head.rs b/src/uu/head/src/head.rs index c60bbfe994a..d6e4db5a78e 100644 --- a/src/uu/head/src/head.rs +++ b/src/uu/head/src/head.rs @@ -3,7 +3,7 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// spell-checker:ignore (vars) zlines BUFWRITER seekable +// spell-checker:ignore (vars) BUFWRITER seekable use clap::{crate_version, Arg, ArgAction, ArgMatches, Command}; use std::ffi::OsString; @@ -31,6 +31,7 @@ mod options { pub const FILES_NAME: &str = "FILE"; pub const PRESUME_INPUT_PIPE: &str = "-PRESUME-INPUT-PIPE"; } + mod parse; mod take; use take::take_all_but; @@ -519,6 +520,7 @@ mod tests { use std::io::Cursor; use super::*; + fn options(args: &str) -> Result { let combined = "head ".to_owned() + args; let args = combined.split_whitespace().map(OsString::from); @@ -526,6 +528,7 @@ mod tests { .get_matches_from(arg_iterate(args).map_err(|_| String::from("Arg iterate failed"))?); HeadOptions::get_from(&matches) } + #[test] fn test_args_modes() { let args = options("-n -10M -vz").unwrap(); @@ -533,6 +536,7 @@ mod tests { assert!(args.verbose); assert_eq!(args.mode, Mode::AllButLastLines(10 * 1024 * 1024)); } + #[test] fn test_gnu_compatibility() { let args = options("-n 1 -c 1 -n 5 -c kiB -vqvqv").unwrap(); // spell-checker:disable-line @@ -542,6 +546,7 @@ mod tests { assert_eq!(options("-2b").unwrap().mode, Mode::FirstBytes(1024)); assert_eq!(options("-5 -c 1").unwrap().mode, Mode::FirstBytes(1)); } + #[test] fn all_args_test() { assert!(options("--silent").unwrap().quiet); @@ -559,11 +564,13 @@ mod tests { assert_eq!(options("--bytes 15").unwrap().mode, Mode::FirstBytes(15)); assert_eq!(options("-c 15").unwrap().mode, Mode::FirstBytes(15)); } + #[test] fn test_options_errors() { assert!(options("-n IsThisTheRealLife?").is_err()); assert!(options("-c IsThisJustFantasy").is_err()); } + #[test] fn test_options_correct_defaults() { let opts = HeadOptions::default(); @@ -574,6 +581,7 @@ mod tests { assert_eq!(opts.mode, Mode::FirstLines(10)); assert!(opts.files.is_empty()); } + fn arg_outputs(src: &str) -> Result { let split = src.split_whitespace().map(OsString::from); match arg_iterate(split) { @@ -586,6 +594,7 @@ mod tests { Err(_) => Err(()), } } + #[test] fn test_arg_iterate() { // test that normal args remain unchanged @@ -610,6 +619,7 @@ mod tests { //test that empty args remain unchanged assert_eq!(arg_outputs("head"), Ok("head".to_owned())); } + #[test] #[cfg(target_os = "linux")] fn test_arg_iterate_bad_encoding() { @@ -618,6 +628,7 @@ mod tests { // this arises from a conversion from OsString to &str assert!(arg_iterate(vec![OsString::from("head"), invalid].into_iter()).is_err()); } + #[test] fn read_early_exit() { let mut empty = std::io::BufReader::new(std::io::Cursor::new(Vec::new())); diff --git a/src/uu/head/src/parse.rs b/src/uu/head/src/parse.rs index 179435c7ae5..7f7dd48f8c5 100644 --- a/src/uu/head/src/parse.rs +++ b/src/uu/head/src/parse.rs @@ -11,6 +11,7 @@ pub enum ParseError { Syntax, Overflow, } + /// Parses obsolete syntax /// head -NUM\[kmzv\] // spell-checker:disable-line pub fn parse_obsolete(src: &str) -> Option, ParseError>> { @@ -135,6 +136,7 @@ pub fn parse_num(src: &str) -> Result<(u64, bool), ParseSizeError> { #[cfg(test)] mod tests { use super::*; + fn obsolete(src: &str) -> Option, ParseError>> { let r = parse_obsolete(src); match r { @@ -145,9 +147,11 @@ mod tests { None => None, } } + fn obsolete_result(src: &[&str]) -> Option, ParseError>> { Some(Ok(src.iter().map(|s| s.to_string()).collect())) } + #[test] fn test_parse_numbers_obsolete() { assert_eq!(obsolete("-5"), obsolete_result(&["-n", "5"])); @@ -167,16 +171,19 @@ mod tests { obsolete_result(&["-z", "-c", "110100480"]) ); } + #[test] fn test_parse_errors_obsolete() { assert_eq!(obsolete("-5n"), Some(Err(ParseError::Syntax))); assert_eq!(obsolete("-5c5"), Some(Err(ParseError::Syntax))); } + #[test] fn test_parse_obsolete_no_match() { assert_eq!(obsolete("-k"), None); assert_eq!(obsolete("asd"), None); } + #[test] #[cfg(target_pointer_width = "64")] fn test_parse_obsolete_overflow_x64() { @@ -189,6 +196,7 @@ mod tests { Some(Err(ParseError::Overflow)) ); } + #[test] #[cfg(target_pointer_width = "32")] fn test_parse_obsolete_overflow_x32() { From 849051f9681b7d65b319942ae0c3bcd8fbe234b4 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 7 Oct 2023 22:22:06 +0000 Subject: [PATCH 0188/2851] chore(deps): update rust crate num-traits to 0.2.17 --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a3c3e709627..197368abf3b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1404,9 +1404,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" dependencies = [ "autocfg", ] diff --git a/Cargo.toml b/Cargo.toml index e59ed7bd7b1..8eecb2b8f65 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -296,7 +296,7 @@ nix = { version = "0.27", default-features = false } nom = "7.1.3" notify = { version = "=6.0.1", features = ["macos_kqueue"] } num-bigint = "0.4.4" -num-traits = "0.2.16" +num-traits = "0.2.17" number_prefix = "0.4" once_cell = "1.18.0" onig = { version = "~6.4", default-features = false } From 7e08562ee6bc71fb5e6b6d0aa19a80549627f5eb Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Sun, 8 Oct 2023 15:02:01 +0200 Subject: [PATCH 0189/2851] expr: add some empty lines --- src/uu/expr/src/syntax_tree.rs | 8 ++++++++ src/uu/expr/src/tokens.rs | 4 ++++ 2 files changed, 12 insertions(+) diff --git a/src/uu/expr/src/syntax_tree.rs b/src/uu/expr/src/syntax_tree.rs index eaa6be1f51e..4ca723a4d8c 100644 --- a/src/uu/expr/src/syntax_tree.rs +++ b/src/uu/expr/src/syntax_tree.rs @@ -31,10 +31,12 @@ pub enum AstNode { operands: OperandsList, }, } + impl AstNode { fn debug_dump(&self) { self.debug_dump_impl(1); } + fn debug_dump_impl(&self, depth: usize) { for _ in 0..depth { print!("\t",); @@ -71,12 +73,14 @@ impl AstNode { operands, }) } + fn new_leaf(token_idx: usize, value: &str) -> Box { Box::new(Self::Leaf { token_idx, value: value.into(), }) } + pub fn evaluate(&self) -> Result { match self { Self::Leaf { value, .. } => Ok(value.clone()), @@ -154,6 +158,7 @@ impl AstNode { }, } } + pub fn operand_values(&self) -> Result, String> { if let Self::Node { operands, op_type, .. @@ -257,6 +262,7 @@ fn ast_from_rpn(rpn: &mut TokenStack) -> Result, String> { } } } + fn maybe_ast_node( token_idx: usize, op_type: &str, @@ -520,6 +526,7 @@ fn prefix_operator_substr(values: &[String]) -> String { fn bool_as_int(b: bool) -> u8 { u8::from(b) } + fn bool_as_string(b: bool) -> String { if b { "1".to_string() @@ -527,6 +534,7 @@ fn bool_as_string(b: bool) -> String { "0".to_string() } } + fn value_as_bool(s: &str) -> bool { if s.is_empty() { return false; diff --git a/src/uu/expr/src/tokens.rs b/src/uu/expr/src/tokens.rs index b4e4c7da58d..3c5cac060f9 100644 --- a/src/uu/expr/src/tokens.rs +++ b/src/uu/expr/src/tokens.rs @@ -38,6 +38,7 @@ pub enum Token { value: String, }, } + impl Token { fn new_infix_op(v: &str, left_assoc: bool, precedence: u8) -> Self { Self::InfixOp { @@ -46,6 +47,7 @@ impl Token { value: v.into(), } } + fn new_value(v: &str) -> Self { Self::Value { value: v.into() } } @@ -56,12 +58,14 @@ impl Token { _ => false, } } + fn is_a_number(&self) -> bool { match self { Self::Value { value, .. } => value.parse::().is_ok(), _ => false, } } + fn is_a_close_paren(&self) -> bool { matches!(*self, Self::ParClose) } From 4ed58718a8bb0b167520c538df2c1722f8fffe45 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 8 Oct 2023 17:31:50 +0200 Subject: [PATCH 0190/2851] github action: extract the warnings into a variable --- .github/workflows/CICD.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index 61c12dad0cd..f7db7b8a253 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -231,10 +231,11 @@ jobs: run: | ## `cargo clippy` lint testing unset fault + CLIPPY_FLAGS="-W clippy::default_trait_access -W clippy::manual_string_new" fault_type="${{ steps.vars.outputs.FAULT_TYPE }}" fault_prefix=$(echo "$fault_type" | tr '[:lower:]' '[:upper:]') # * convert any warnings to GHA UI annotations; ref: - S=$(cargo clippy --all-targets ${{ matrix.job.cargo-options }} ${{ steps.vars.outputs.CARGO_UTILITY_LIST_OPTIONS }} -- -W clippy::default_trait_access -W clippy::manual_string_new -D warnings 2>&1) && printf "%s\n" "$S" || { printf "%s\n" "$S" ; printf "%s" "$S" | sed -E -n -e '/^error:/{' -e "N; s/^error:[[:space:]]+(.*)\\n[[:space:]]+-->[[:space:]]+(.*):([0-9]+):([0-9]+).*$/::${fault_type} file=\2,line=\3,col=\4::${fault_prefix}: \`cargo clippy\`: \1 (file:'\2', line:\3)/p;" -e '}' ; fault=true ; } + S=$(cargo clippy --all-targets ${{ matrix.job.cargo-options }} ${{ steps.vars.outputs.CARGO_UTILITY_LIST_OPTIONS }} -- ${CLIPPY_FLAGS} -D warnings 2>&1) && printf "%s\n" "$S" || { printf "%s\n" "$S" ; printf "%s" "$S" | sed -E -n -e '/^error:/{' -e "N; s/^error:[[:space:]]+(.*)\\n[[:space:]]+-->[[:space:]]+(.*):([0-9]+):([0-9]+).*$/::${fault_type} file=\2,line=\3,col=\4::${fault_prefix}: \`cargo clippy\`: \1 (file:'\2', line:\3)/p;" -e '}' ; fault=true ; } if [ -n "${{ steps.vars.outputs.FAIL_ON_FAULT }}" ] && [ -n "$fault" ]; then exit 1 ; fi style_spellcheck: From 6d245491563d5f40c7cfece8ffcfa49695e5ddc3 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 8 Oct 2023 17:32:03 +0200 Subject: [PATCH 0191/2851] github action: enable clippy::cognitive_complexity --- .github/workflows/CICD.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index f7db7b8a253..b448b76d06f 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -231,7 +231,7 @@ jobs: run: | ## `cargo clippy` lint testing unset fault - CLIPPY_FLAGS="-W clippy::default_trait_access -W clippy::manual_string_new" + CLIPPY_FLAGS="-W clippy::default_trait_access -W clippy::manual_string_new -W clippy::cognitive_complexity" fault_type="${{ steps.vars.outputs.FAULT_TYPE }}" fault_prefix=$(echo "$fault_type" | tr '[:lower:]' '[:upper:]') # * convert any warnings to GHA UI annotations; ref: From 5ac1aef20ec3d639db72207e537d84750c828c87 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 8 Oct 2023 17:34:37 +0200 Subject: [PATCH 0192/2851] github action: move the fuzzing action into it own file/task --- .github/workflows/CICD.yml | 50 --------------------------- .github/workflows/fuzzing.yml | 64 +++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 50 deletions(-) create mode 100644 .github/workflows/fuzzing.yml diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index 61c12dad0cd..39eea159326 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -125,56 +125,6 @@ jobs: S=$(cargo fmt -- --check) && printf "%s\n" "$S" || { printf "%s\n" "$S" ; printf "%s\n" "$S" | sed -E -n -e "s/^Diff[[:space:]]+in[[:space:]]+${PWD//\//\\/}\/(.*)[[:space:]]+at[[:space:]]+[^0-9]+([0-9]+).*$/::${fault_type} file=\1,line=\2::${fault_prefix}: \`cargo fmt\`: style violation (file:'\1', line:\2; use \`cargo fmt -- \"\1\"\`)/p" ; fault=true ; } if [ -n "${{ steps.vars.outputs.FAIL_ON_FAULT }}" ] && [ -n "$fault" ]; then exit 1 ; fi - fuzz: - name: Run the fuzzers - runs-on: ubuntu-latest - env: - RUN_FOR: 60 - steps: - - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@nightly - - name: Install `cargo-fuzz` - run: cargo install cargo-fuzz - - uses: Swatinem/rust-cache@v2 - - name: Run fuzz_date for XX seconds - shell: bash - run: | - ## Run it - cd fuzz - cargo +nightly fuzz run fuzz_date -- -max_total_time=${{ env.RUN_FOR }} -detect_leaks=0 - - name: Run fuzz_test for XX seconds - continue-on-error: true - shell: bash - run: | - ## Run it - cd fuzz - cargo +nightly fuzz run fuzz_test -- -max_total_time=${{ env.RUN_FOR }} -detect_leaks=0 - - name: Run fuzz_expr for XX seconds - continue-on-error: true - shell: bash - run: | - ## Run it - cd fuzz - cargo +nightly fuzz run fuzz_expr -- -max_total_time=${{ env.RUN_FOR }} -detect_leaks=0 - - name: Run fuzz_parse_glob for XX seconds - shell: bash - run: | - ## Run it - cd fuzz - cargo +nightly fuzz run fuzz_parse_glob -- -max_total_time=${{ env.RUN_FOR }} -detect_leaks=0 - - name: Run fuzz_parse_size for XX seconds - shell: bash - run: | - ## Run it - cd fuzz - cargo +nightly fuzz run fuzz_parse_size -- -max_total_time=${{ env.RUN_FOR }} -detect_leaks=0 - - name: Run fuzz_parse_time for XX seconds - shell: bash - run: | - ## Run it - cd fuzz - cargo +nightly fuzz run fuzz_parse_time -- -max_total_time=${{ env.RUN_FOR }} -detect_leaks=0 - style_lint: name: Style/lint runs-on: ${{ matrix.job.os }} diff --git a/.github/workflows/fuzzing.yml b/.github/workflows/fuzzing.yml new file mode 100644 index 00000000000..677df2c9f18 --- /dev/null +++ b/.github/workflows/fuzzing.yml @@ -0,0 +1,64 @@ +name: Fuzzing + +# spell-checker:ignore fuzzer + +on: [push, pull_request] + +permissions: + contents: read # to fetch code (actions/checkout) + +# End the current execution if there is a new changeset in the PR. +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} + +jobs: + fuzz: + name: Run the fuzzers + runs-on: ubuntu-latest + env: + RUN_FOR: 60 + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@nightly + - name: Install `cargo-fuzz` + run: cargo install cargo-fuzz + - uses: Swatinem/rust-cache@v2 + - name: Run fuzz_date for XX seconds + shell: bash + run: | + ## Run it + cd fuzz + cargo +nightly fuzz run fuzz_date -- -max_total_time=${{ env.RUN_FOR }} -detect_leaks=0 + - name: Run fuzz_test for XX seconds + continue-on-error: true + shell: bash + run: | + ## Run it + cd fuzz + cargo +nightly fuzz run fuzz_test -- -max_total_time=${{ env.RUN_FOR }} -detect_leaks=0 + - name: Run fuzz_expr for XX seconds + continue-on-error: true + shell: bash + run: | + ## Run it + cd fuzz + cargo +nightly fuzz run fuzz_expr -- -max_total_time=${{ env.RUN_FOR }} -detect_leaks=0 + - name: Run fuzz_parse_glob for XX seconds + shell: bash + run: | + ## Run it + cd fuzz + cargo +nightly fuzz run fuzz_parse_glob -- -max_total_time=${{ env.RUN_FOR }} -detect_leaks=0 + - name: Run fuzz_parse_size for XX seconds + shell: bash + run: | + ## Run it + cd fuzz + cargo +nightly fuzz run fuzz_parse_size -- -max_total_time=${{ env.RUN_FOR }} -detect_leaks=0 + - name: Run fuzz_parse_time for XX seconds + shell: bash + run: | + ## Run it + cd fuzz + cargo +nightly fuzz run fuzz_parse_time -- -max_total_time=${{ env.RUN_FOR }} -detect_leaks=0 From 8e8a91be5d4d9628542e23f0c4daaaced42622e4 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 8 Oct 2023 17:46:11 +0200 Subject: [PATCH 0193/2851] Ignore more cognitive_complexity --- src/uu/cp/src/copydir.rs | 1 + src/uu/id/src/id.rs | 1 + src/uu/install/src/install.rs | 1 + src/uu/od/src/inputdecoder.rs | 1 + src/uu/od/src/output_info.rs | 1 + src/uu/od/src/parse_inputs.rs | 2 ++ src/uu/od/src/parse_nrofbytes.rs | 1 + src/uu/od/src/peekreader.rs | 1 + src/uu/od/src/prn_char.rs | 2 ++ src/uu/od/src/prn_float.rs | 2 ++ src/uu/od/src/prn_int.rs | 2 ++ src/uu/stat/src/stat.rs | 2 ++ 12 files changed, 17 insertions(+) diff --git a/src/uu/cp/src/copydir.rs b/src/uu/cp/src/copydir.rs index ac44ce68752..a8b941364a7 100644 --- a/src/uu/cp/src/copydir.rs +++ b/src/uu/cp/src/copydir.rs @@ -447,6 +447,7 @@ mod tests { use super::ends_with_slash_dot; #[test] + #[allow(clippy::cognitive_complexity)] fn test_ends_with_slash_dot() { assert!(ends_with_slash_dot("/.")); assert!(ends_with_slash_dot("./.")); diff --git a/src/uu/id/src/id.rs b/src/uu/id/src/id.rs index 49ad549257d..8b16ba8b733 100644 --- a/src/uu/id/src/id.rs +++ b/src/uu/id/src/id.rs @@ -109,6 +109,7 @@ struct State { } #[uucore::main] +#[allow(clippy::cognitive_complexity)] pub fn uumain(args: impl uucore::Args) -> UResult<()> { let matches = uu_app().after_help(AFTER_HELP).try_get_matches_from(args)?; diff --git a/src/uu/install/src/install.rs b/src/uu/install/src/install.rs index 02cf8345ddf..63ba52b1c8d 100644 --- a/src/uu/install/src/install.rs +++ b/src/uu/install/src/install.rs @@ -526,6 +526,7 @@ fn is_potential_directory_path(path: &Path) -> bool { /// /// Returns a Result type with the Err variant containing the error message. /// +#[allow(clippy::cognitive_complexity)] fn standard(mut paths: Vec, b: &Behavior) -> UResult<()> { // first check that paths contains at least one element if paths.is_empty() { diff --git a/src/uu/od/src/inputdecoder.rs b/src/uu/od/src/inputdecoder.rs index 6f08eba124e..e63eaed148f 100644 --- a/src/uu/od/src/inputdecoder.rs +++ b/src/uu/od/src/inputdecoder.rs @@ -166,6 +166,7 @@ mod tests { #[test] #[allow(clippy::float_cmp)] + #[allow(clippy::cognitive_complexity)] fn smoke_test() { let data = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0xff, 0xff]; let mut input = PeekReader::new(Cursor::new(&data)); diff --git a/src/uu/od/src/output_info.rs b/src/uu/od/src/output_info.rs index 96c01f906ed..993bba32918 100644 --- a/src/uu/od/src/output_info.rs +++ b/src/uu/od/src/output_info.rs @@ -208,6 +208,7 @@ impl TypeSizeInfo for TypeInfo { } #[test] +#[allow(clippy::cognitive_complexity)] fn test_calculate_alignment() { // For this example `byte_size_block` is 8 and 'print_width_block' is 23: // 1777777777777777777777 1777777777777777777777 diff --git a/src/uu/od/src/parse_inputs.rs b/src/uu/od/src/parse_inputs.rs index 74dc5a13a83..241d842af9f 100644 --- a/src/uu/od/src/parse_inputs.rs +++ b/src/uu/od/src/parse_inputs.rs @@ -213,6 +213,7 @@ mod tests { } #[test] + #[allow(clippy::cognitive_complexity)] fn test_parse_inputs_with_offset() { // offset is found without filename, so stdin will be used. assert_eq!( @@ -355,6 +356,7 @@ mod tests { } #[test] + #[allow(clippy::cognitive_complexity)] fn test_parse_offset_operand() { assert_eq!(8, parse_offset_operand_str("10").unwrap()); // default octal assert_eq!(0, parse_offset_operand_str("0").unwrap()); diff --git a/src/uu/od/src/parse_nrofbytes.rs b/src/uu/od/src/parse_nrofbytes.rs index a5c81f68ba9..431b2a71fde 100644 --- a/src/uu/od/src/parse_nrofbytes.rs +++ b/src/uu/od/src/parse_nrofbytes.rs @@ -79,6 +79,7 @@ pub fn parse_number_of_bytes(s: &str) -> Result { } #[test] +#[allow(clippy::cognitive_complexity)] fn test_parse_number_of_bytes() { // octal input assert_eq!(8, parse_number_of_bytes("010").unwrap()); diff --git a/src/uu/od/src/peekreader.rs b/src/uu/od/src/peekreader.rs index 239faab92ee..82f139c726e 100644 --- a/src/uu/od/src/peekreader.rs +++ b/src/uu/od/src/peekreader.rs @@ -186,6 +186,7 @@ mod tests { } #[test] + #[allow(clippy::cognitive_complexity)] fn test_peek_read_with_smaller_buffer() { let mut sut = PeekReader::new(Cursor::new(&b"abcdefghij"[..])); diff --git a/src/uu/od/src/prn_char.rs b/src/uu/od/src/prn_char.rs index db12d9d4fc4..36a00a67bb3 100644 --- a/src/uu/od/src/prn_char.rs +++ b/src/uu/od/src/prn_char.rs @@ -99,6 +99,7 @@ pub fn format_ascii_dump(bytes: &[u8]) -> String { } #[test] +#[allow(clippy::cognitive_complexity)] fn test_format_item_a() { assert_eq!(" nul", format_item_a(0x00)); assert_eq!(" soh", format_item_a(0x01)); @@ -114,6 +115,7 @@ fn test_format_item_a() { } #[test] +#[allow(clippy::cognitive_complexity)] fn test_format_item_c() { assert_eq!(" \\0", format_item_c(&[0x00])); assert_eq!(" 001", format_item_c(&[0x01])); diff --git a/src/uu/od/src/prn_float.rs b/src/uu/od/src/prn_float.rs index af632d66b24..f4d018bd84f 100644 --- a/src/uu/od/src/prn_float.rs +++ b/src/uu/od/src/prn_float.rs @@ -91,6 +91,7 @@ fn format_float(f: f64, width: usize, precision: usize) -> String { #[test] #[allow(clippy::excessive_precision)] +#[allow(clippy::cognitive_complexity)] fn test_format_flo32() { assert_eq!(format_flo32(1.0), " 1.0000000"); assert_eq!(format_flo32(9.9999990), " 9.9999990"); @@ -167,6 +168,7 @@ fn test_format_flo32() { } #[test] +#[allow(clippy::cognitive_complexity)] fn test_format_flo64() { assert_eq!(format_flo64(1.0), " 1.0000000000000000"); assert_eq!(format_flo64(10.0), " 10.000000000000000"); diff --git a/src/uu/od/src/prn_int.rs b/src/uu/od/src/prn_int.rs index c214b7525eb..f843ff77ca2 100644 --- a/src/uu/od/src/prn_int.rs +++ b/src/uu/od/src/prn_int.rs @@ -89,6 +89,7 @@ int_writer_signed!(FORMAT_ITEM_DEC32S, 4, 12, format_item_dec_s32, DEC!()); // m int_writer_signed!(FORMAT_ITEM_DEC64S, 8, 21, format_item_dec_s64, DEC!()); // max: -9223372036854775808 #[test] +#[allow(clippy::cognitive_complexity)] fn test_sign_extend() { assert_eq!( 0xffff_ffff_ffff_ff80u64 as i64, @@ -179,6 +180,7 @@ fn test_format_item_dec_u() { } #[test] +#[allow(clippy::cognitive_complexity)] fn test_format_item_dec_s() { assert_eq!(" 0", format_item_dec_s8(0)); assert_eq!(" 127", format_item_dec_s8(0x7f)); diff --git a/src/uu/stat/src/stat.rs b/src/uu/stat/src/stat.rs index c36b450067b..055393578e2 100644 --- a/src/uu/stat/src/stat.rs +++ b/src/uu/stat/src/stat.rs @@ -404,6 +404,7 @@ fn print_unsigned_hex( } impl Stater { + #[allow(clippy::cognitive_complexity)] fn generate_tokens(format_str: &str, use_printf: bool) -> UResult> { let mut tokens = Vec::new(); let bound = format_str.len(); @@ -609,6 +610,7 @@ impl Stater { ret } + #[allow(clippy::cognitive_complexity)] fn do_stat(&self, file: &OsStr, stdin_is_fifo: bool) -> i32 { let display_name = file.to_string_lossy(); let file = if cfg!(unix) && display_name == "-" { From 4d122d7dd0e03d2e3f30ad91576947ee06e616b8 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 8 Oct 2023 17:51:06 +0200 Subject: [PATCH 0194/2851] Move rustfmt + clippy + typo check into it own workflow --- .github/workflows/CICD.yml | 160 -------------------------- .github/workflows/code-quality.yml | 175 +++++++++++++++++++++++++++++ 2 files changed, 175 insertions(+), 160 deletions(-) create mode 100644 .github/workflows/code-quality.yml diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index b448b76d06f..3b78fa6e2a7 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -82,49 +82,6 @@ jobs: grep --ignore-case "all deps seem to have been used" udeps.log || { printf "%s\n" "::${fault_type} ::${fault_prefix}: \`cargo udeps\`: style violation (unused dependency found)" ; fault=true ; } if [ -n "${{ steps.vars.outputs.FAIL_ON_FAULT }}" ] && [ -n "$fault" ]; then exit 1 ; fi - style_format: - name: Style/format - runs-on: ${{ matrix.job.os }} - strategy: - fail-fast: false - matrix: - job: - - { os: ubuntu-latest , features: feat_os_unix } - steps: - - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@master - with: - toolchain: stable - components: rustfmt - - uses: Swatinem/rust-cache@v2 - - name: Initialize workflow variables - id: vars - shell: bash - run: | - ## VARs setup - outputs() { step_id="${{ github.action }}"; for var in "$@" ; do echo steps.${step_id}.outputs.${var}="${!var}"; echo "${var}=${!var}" >> $GITHUB_OUTPUT; done; } - # failure mode - unset FAIL_ON_FAULT ; case '${{ env.STYLE_FAIL_ON_FAULT }}' in - ''|0|f|false|n|no|off) FAULT_TYPE=warning ;; - *) FAIL_ON_FAULT=true ; FAULT_TYPE=error ;; - esac; - outputs FAIL_ON_FAULT FAULT_TYPE - # target-specific options - # * CARGO_FEATURES_OPTION - CARGO_FEATURES_OPTION='' ; - if [ -n "${{ matrix.job.features }}" ]; then CARGO_FEATURES_OPTION='--features "${{ matrix.job.features }}"' ; fi - outputs CARGO_FEATURES_OPTION - - name: "`cargo fmt` testing" - shell: bash - run: | - ## `cargo fmt` testing - unset fault - fault_type="${{ steps.vars.outputs.FAULT_TYPE }}" - fault_prefix=$(echo "$fault_type" | tr '[:lower:]' '[:upper:]') - # * convert any errors/warnings to GHA UI annotations; ref: - S=$(cargo fmt -- --check) && printf "%s\n" "$S" || { printf "%s\n" "$S" ; printf "%s\n" "$S" | sed -E -n -e "s/^Diff[[:space:]]+in[[:space:]]+${PWD//\//\\/}\/(.*)[[:space:]]+at[[:space:]]+[^0-9]+([0-9]+).*$/::${fault_type} file=\1,line=\2::${fault_prefix}: \`cargo fmt\`: style violation (file:'\1', line:\2; use \`cargo fmt -- \"\1\"\`)/p" ; fault=true ; } - if [ -n "${{ steps.vars.outputs.FAIL_ON_FAULT }}" ] && [ -n "$fault" ]; then exit 1 ; fi - fuzz: name: Run the fuzzers runs-on: ubuntu-latest @@ -175,114 +132,6 @@ jobs: cd fuzz cargo +nightly fuzz run fuzz_parse_time -- -max_total_time=${{ env.RUN_FOR }} -detect_leaks=0 - style_lint: - name: Style/lint - runs-on: ${{ matrix.job.os }} - env: - SCCACHE_GHA_ENABLED: "true" - RUSTC_WRAPPER: "sccache" - strategy: - fail-fast: false - matrix: - job: - - { os: ubuntu-latest , features: feat_os_unix } - - { os: macos-latest , features: feat_os_macos } - - { os: windows-latest , features: feat_os_windows } - steps: - - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@master - with: - toolchain: stable - components: clippy - - uses: Swatinem/rust-cache@v2 - - name: Run sccache-cache - uses: mozilla-actions/sccache-action@v0.0.3 - - name: Initialize workflow variables - id: vars - shell: bash - run: | - ## VARs setup - outputs() { step_id="${{ github.action }}"; for var in "$@" ; do echo steps.${step_id}.outputs.${var}="${!var}"; echo "${var}=${!var}" >> $GITHUB_OUTPUT; done; } - # failure mode - unset FAIL_ON_FAULT ; case '${{ env.STYLE_FAIL_ON_FAULT }}' in - ''|0|f|false|n|no|off) FAULT_TYPE=warning ;; - *) FAIL_ON_FAULT=true ; FAULT_TYPE=error ;; - esac; - outputs FAIL_ON_FAULT FAULT_TYPE - # target-specific options - # * CARGO_FEATURES_OPTION - CARGO_FEATURES_OPTION='--all-features' ; - if [ -n "${{ matrix.job.features }}" ]; then CARGO_FEATURES_OPTION='--features ${{ matrix.job.features }}' ; fi - outputs CARGO_FEATURES_OPTION - # * determine sub-crate utility list - UTILITY_LIST="$(./util/show-utils.sh ${CARGO_FEATURES_OPTION})" - echo UTILITY_LIST=${UTILITY_LIST} - CARGO_UTILITY_LIST_OPTIONS="$(for u in ${UTILITY_LIST}; do echo -n "-puu_${u} "; done;)" - outputs CARGO_UTILITY_LIST_OPTIONS - - name: Install/setup prerequisites - shell: bash - run: | - ## Install/setup prerequisites - case '${{ matrix.job.os }}' in - macos-latest) brew install coreutils ;; # needed for show-utils.sh - esac - - name: "`cargo clippy` lint testing" - shell: bash - run: | - ## `cargo clippy` lint testing - unset fault - CLIPPY_FLAGS="-W clippy::default_trait_access -W clippy::manual_string_new -W clippy::cognitive_complexity" - fault_type="${{ steps.vars.outputs.FAULT_TYPE }}" - fault_prefix=$(echo "$fault_type" | tr '[:lower:]' '[:upper:]') - # * convert any warnings to GHA UI annotations; ref: - S=$(cargo clippy --all-targets ${{ matrix.job.cargo-options }} ${{ steps.vars.outputs.CARGO_UTILITY_LIST_OPTIONS }} -- ${CLIPPY_FLAGS} -D warnings 2>&1) && printf "%s\n" "$S" || { printf "%s\n" "$S" ; printf "%s" "$S" | sed -E -n -e '/^error:/{' -e "N; s/^error:[[:space:]]+(.*)\\n[[:space:]]+-->[[:space:]]+(.*):([0-9]+):([0-9]+).*$/::${fault_type} file=\2,line=\3,col=\4::${fault_prefix}: \`cargo clippy\`: \1 (file:'\2', line:\3)/p;" -e '}' ; fault=true ; } - if [ -n "${{ steps.vars.outputs.FAIL_ON_FAULT }}" ] && [ -n "$fault" ]; then exit 1 ; fi - - style_spellcheck: - name: Style/spelling - runs-on: ${{ matrix.job.os }} - strategy: - matrix: - job: - - { os: ubuntu-latest , features: feat_os_unix } - steps: - - uses: actions/checkout@v4 - - name: Initialize workflow variables - id: vars - shell: bash - run: | - ## VARs setup - outputs() { step_id="${{ github.action }}"; for var in "$@" ; do echo steps.${step_id}.outputs.${var}="${!var}"; echo "${var}=${!var}" >> $GITHUB_OUTPUT; done; } - # failure mode - unset FAIL_ON_FAULT ; case '${{ env.STYLE_FAIL_ON_FAULT }}' in - ''|0|f|false|n|no|off) FAULT_TYPE=warning ;; - *) FAIL_ON_FAULT=true ; FAULT_TYPE=error ;; - esac; - outputs FAIL_ON_FAULT FAULT_TYPE - - name: Install/setup prerequisites - shell: bash - run: | - ## Install/setup prerequisites - # * pin installed cspell to v4.2.8 (cspell v5+ is broken for NodeJS < v12) - ## maint: [2021-11-10; rivy] `cspell` version may be advanced to v5 when used with NodeJS >= v12 - sudo apt-get -y update ; sudo apt-get -y install npm ; sudo npm install cspell@4.2.8 -g ; - - name: Run `cspell` - shell: bash - run: | - ## Run `cspell` - unset fault - fault_type="${{ steps.vars.outputs.FAULT_TYPE }}" - fault_prefix=$(echo "$fault_type" | tr '[:lower:]' '[:upper:]') - # * find cspell configuration ; note: avoid quotes around ${cfg_file} b/c `cspell` (v4) doesn't correctly dequote the config argument (or perhaps a subshell expansion issue?) - cfg_files=($(shopt -s nullglob ; echo {.vscode,.}/{,.}c[sS]pell{.json,.config{.js,.cjs,.json,.yaml,.yml},.yaml,.yml} ;)) - cfg_file=${cfg_files[0]} - unset CSPELL_CFG_OPTION ; if [ -n "$cfg_file" ]; then CSPELL_CFG_OPTION="--config $cfg_file" ; fi - # * `cspell` - ## maint: [2021-11-10; rivy] the `--no-progress` option for `cspell` is a `cspell` v5+ option - # S=$(cspell ${CSPELL_CFG_OPTION} --no-summary --no-progress "**/*") && printf "%s\n" "$S" || { printf "%s\n" "$S" ; printf "%s" "$S" | sed -E -n "s/${PWD//\//\\/}\/(.*):(.*):(.*) - (.*)/::${fault_type} file=\1,line=\2,col=\3::${fault_type^^}: \4 (file:'\1', line:\2)/p" ; fault=true ; true ; } - S=$(cspell ${CSPELL_CFG_OPTION} --no-summary "**/*") && printf "%s\n" "$S" || { printf "%s\n" "$S" ; printf "%s" "$S" | sed -E -n "s/${PWD//\//\\/}\/(.*):(.*):(.*) - (.*)/::${fault_type} file=\1,line=\2,col=\3::${fault_type^^}: \4 (file:'\1', line:\2)/p" ; fault=true ; true ; } - if [ -n "${{ steps.vars.outputs.FAIL_ON_FAULT }}" ] && [ -n "$fault" ]; then exit 1 ; fi - doc_warnings: name: Documentation/warnings runs-on: ${{ matrix.job.os }} @@ -1081,15 +930,6 @@ jobs: name: toybox-result.json path: ${{ steps.vars.outputs.TEST_SUMMARY_FILE }} - toml_format: - runs-on: ubuntu-latest - steps: - - name: Clone repository - uses: actions/checkout@v4 - - - name: Check - run: npx --yes @taplo/cli fmt --check - coverage: name: Code Coverage runs-on: ${{ matrix.job.os }} diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml new file mode 100644 index 00000000000..c67483280f0 --- /dev/null +++ b/.github/workflows/code-quality.yml @@ -0,0 +1,175 @@ +name: Code Quality + +# spell-checker:ignore TERMUX reactivecircus Swatinem noaudio pkill swiftshader dtolnay juliangruber + +on: [push, pull_request] + +permissions: + contents: read # to fetch code (actions/checkout) + +# End the current execution if there is a new changeset in the PR. +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} + +jobs: + + style_format: + name: Style/format + runs-on: ${{ matrix.job.os }} + strategy: + fail-fast: false + matrix: + job: + - { os: ubuntu-latest , features: feat_os_unix } + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: stable + components: rustfmt + - uses: Swatinem/rust-cache@v2 + - name: Initialize workflow variables + id: vars + shell: bash + run: | + ## VARs setup + outputs() { step_id="${{ github.action }}"; for var in "$@" ; do echo steps.${step_id}.outputs.${var}="${!var}"; echo "${var}=${!var}" >> $GITHUB_OUTPUT; done; } + # failure mode + unset FAIL_ON_FAULT ; case '${{ env.STYLE_FAIL_ON_FAULT }}' in + ''|0|f|false|n|no|off) FAULT_TYPE=warning ;; + *) FAIL_ON_FAULT=true ; FAULT_TYPE=error ;; + esac; + outputs FAIL_ON_FAULT FAULT_TYPE + # target-specific options + # * CARGO_FEATURES_OPTION + CARGO_FEATURES_OPTION='' ; + if [ -n "${{ matrix.job.features }}" ]; then CARGO_FEATURES_OPTION='--features "${{ matrix.job.features }}"' ; fi + outputs CARGO_FEATURES_OPTION + - name: "`cargo fmt` testing" + shell: bash + run: | + ## `cargo fmt` testing + unset fault + fault_type="${{ steps.vars.outputs.FAULT_TYPE }}" + fault_prefix=$(echo "$fault_type" | tr '[:lower:]' '[:upper:]') + # * convert any errors/warnings to GHA UI annotations; ref: + S=$(cargo fmt -- --check) && printf "%s\n" "$S" || { printf "%s\n" "$S" ; printf "%s\n" "$S" | sed -E -n -e "s/^Diff[[:space:]]+in[[:space:]]+${PWD//\//\\/}\/(.*)[[:space:]]+at[[:space:]]+[^0-9]+([0-9]+).*$/::${fault_type} file=\1,line=\2::${fault_prefix}: \`cargo fmt\`: style violation (file:'\1', line:\2; use \`cargo fmt -- \"\1\"\`)/p" ; fault=true ; } + if [ -n "${{ steps.vars.outputs.FAIL_ON_FAULT }}" ] && [ -n "$fault" ]; then exit 1 ; fi + + style_lint: + name: Style/lint + runs-on: ${{ matrix.job.os }} + env: + SCCACHE_GHA_ENABLED: "true" + RUSTC_WRAPPER: "sccache" + strategy: + fail-fast: false + matrix: + job: + - { os: ubuntu-latest , features: feat_os_unix } + - { os: macos-latest , features: feat_os_macos } + - { os: windows-latest , features: feat_os_windows } + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: stable + components: clippy + - uses: Swatinem/rust-cache@v2 + - name: Run sccache-cache + uses: mozilla-actions/sccache-action@v0.0.3 + - name: Initialize workflow variables + id: vars + shell: bash + run: | + ## VARs setup + outputs() { step_id="${{ github.action }}"; for var in "$@" ; do echo steps.${step_id}.outputs.${var}="${!var}"; echo "${var}=${!var}" >> $GITHUB_OUTPUT; done; } + # failure mode + unset FAIL_ON_FAULT ; case '${{ env.STYLE_FAIL_ON_FAULT }}' in + ''|0|f|false|n|no|off) FAULT_TYPE=warning ;; + *) FAIL_ON_FAULT=true ; FAULT_TYPE=error ;; + esac; + outputs FAIL_ON_FAULT FAULT_TYPE + # target-specific options + # * CARGO_FEATURES_OPTION + CARGO_FEATURES_OPTION='--all-features' ; + if [ -n "${{ matrix.job.features }}" ]; then CARGO_FEATURES_OPTION='--features ${{ matrix.job.features }}' ; fi + outputs CARGO_FEATURES_OPTION + # * determine sub-crate utility list + UTILITY_LIST="$(./util/show-utils.sh ${CARGO_FEATURES_OPTION})" + echo UTILITY_LIST=${UTILITY_LIST} + CARGO_UTILITY_LIST_OPTIONS="$(for u in ${UTILITY_LIST}; do echo -n "-puu_${u} "; done;)" + outputs CARGO_UTILITY_LIST_OPTIONS + - name: Install/setup prerequisites + shell: bash + run: | + ## Install/setup prerequisites + case '${{ matrix.job.os }}' in + macos-latest) brew install coreutils ;; # needed for show-utils.sh + esac + - name: "`cargo clippy` lint testing" + shell: bash + run: | + ## `cargo clippy` lint testing + unset fault + CLIPPY_FLAGS="-W clippy::default_trait_access -W clippy::manual_string_new -W clippy::cognitive_complexity" + fault_type="${{ steps.vars.outputs.FAULT_TYPE }}" + fault_prefix=$(echo "$fault_type" | tr '[:lower:]' '[:upper:]') + # * convert any warnings to GHA UI annotations; ref: + S=$(cargo clippy --all-targets ${{ matrix.job.cargo-options }} ${{ steps.vars.outputs.CARGO_UTILITY_LIST_OPTIONS }} -- ${CLIPPY_FLAGS} -D warnings 2>&1) && printf "%s\n" "$S" || { printf "%s\n" "$S" ; printf "%s" "$S" | sed -E -n -e '/^error:/{' -e "N; s/^error:[[:space:]]+(.*)\\n[[:space:]]+-->[[:space:]]+(.*):([0-9]+):([0-9]+).*$/::${fault_type} file=\2,line=\3,col=\4::${fault_prefix}: \`cargo clippy\`: \1 (file:'\2', line:\3)/p;" -e '}' ; fault=true ; } + if [ -n "${{ steps.vars.outputs.FAIL_ON_FAULT }}" ] && [ -n "$fault" ]; then exit 1 ; fi + + style_spellcheck: + name: Style/spelling + runs-on: ${{ matrix.job.os }} + strategy: + matrix: + job: + - { os: ubuntu-latest , features: feat_os_unix } + steps: + - uses: actions/checkout@v4 + - name: Initialize workflow variables + id: vars + shell: bash + run: | + ## VARs setup + outputs() { step_id="${{ github.action }}"; for var in "$@" ; do echo steps.${step_id}.outputs.${var}="${!var}"; echo "${var}=${!var}" >> $GITHUB_OUTPUT; done; } + # failure mode + unset FAIL_ON_FAULT ; case '${{ env.STYLE_FAIL_ON_FAULT }}' in + ''|0|f|false|n|no|off) FAULT_TYPE=warning ;; + *) FAIL_ON_FAULT=true ; FAULT_TYPE=error ;; + esac; + outputs FAIL_ON_FAULT FAULT_TYPE + - name: Install/setup prerequisites + shell: bash + run: | + ## Install/setup prerequisites + # * pin installed cspell to v4.2.8 (cspell v5+ is broken for NodeJS < v12) + ## maint: [2021-11-10; rivy] `cspell` version may be advanced to v5 when used with NodeJS >= v12 + sudo apt-get -y update ; sudo apt-get -y install npm ; sudo npm install cspell@4.2.8 -g ; + - name: Run `cspell` + shell: bash + run: | + ## Run `cspell` + unset fault + fault_type="${{ steps.vars.outputs.FAULT_TYPE }}" + fault_prefix=$(echo "$fault_type" | tr '[:lower:]' '[:upper:]') + # * find cspell configuration ; note: avoid quotes around ${cfg_file} b/c `cspell` (v4) doesn't correctly dequote the config argument (or perhaps a subshell expansion issue?) + cfg_files=($(shopt -s nullglob ; echo {.vscode,.}/{,.}c[sS]pell{.json,.config{.js,.cjs,.json,.yaml,.yml},.yaml,.yml} ;)) + cfg_file=${cfg_files[0]} + unset CSPELL_CFG_OPTION ; if [ -n "$cfg_file" ]; then CSPELL_CFG_OPTION="--config $cfg_file" ; fi + # * `cspell` + ## maint: [2021-11-10; rivy] the `--no-progress` option for `cspell` is a `cspell` v5+ option + # S=$(cspell ${CSPELL_CFG_OPTION} --no-summary --no-progress "**/*") && printf "%s\n" "$S" || { printf "%s\n" "$S" ; printf "%s" "$S" | sed -E -n "s/${PWD//\//\\/}\/(.*):(.*):(.*) - (.*)/::${fault_type} file=\1,line=\2,col=\3::${fault_type^^}: \4 (file:'\1', line:\2)/p" ; fault=true ; true ; } + S=$(cspell ${CSPELL_CFG_OPTION} --no-summary "**/*") && printf "%s\n" "$S" || { printf "%s\n" "$S" ; printf "%s" "$S" | sed -E -n "s/${PWD//\//\\/}\/(.*):(.*):(.*) - (.*)/::${fault_type} file=\1,line=\2,col=\3::${fault_type^^}: \4 (file:'\1', line:\2)/p" ; fault=true ; true ; } + if [ -n "${{ steps.vars.outputs.FAIL_ON_FAULT }}" ] && [ -n "$fault" ]; then exit 1 ; fi + + toml_format: + runs-on: ubuntu-latest + steps: + - name: Clone repository + uses: actions/checkout@v4 + + - name: Check + run: npx --yes @taplo/cli fmt --check From 02ab93c141f3b17d713276226ff1eaa555847c6c Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 8 Oct 2023 17:54:36 +0200 Subject: [PATCH 0195/2851] github action: name the toml task --- .github/workflows/code-quality.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml index c67483280f0..abcfd90ab96 100644 --- a/.github/workflows/code-quality.yml +++ b/.github/workflows/code-quality.yml @@ -166,6 +166,7 @@ jobs: if [ -n "${{ steps.vars.outputs.FAIL_ON_FAULT }}" ] && [ -n "$fault" ]; then exit 1 ; fi toml_format: + name: Style/toml runs-on: ubuntu-latest steps: - name: Clone repository From 2c9e091ebee4e9d5b85ac46015d8727ae654ffdc Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Mon, 9 Oct 2023 07:07:38 +0200 Subject: [PATCH 0196/2851] Bump errno from 0.3.1 to 0.3.5 --- Cargo.lock | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 197368abf3b..d4da117364d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -783,25 +783,14 @@ dependencies = [ [[package]] name = "errno" -version = "0.3.1" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" +checksum = "ac3e13f66a2f95e32a39eaa81f6b95d42878ca0e1db0c7543723dfe12557e860" dependencies = [ - "errno-dragonfly", "libc", "windows-sys 0.48.0", ] -[[package]] -name = "errno-dragonfly" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" -dependencies = [ - "cc", - "libc", -] - [[package]] name = "exacl" version = "0.11.0" From 2883c0a9689ead143ae4a8b38536447939d890fd Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Mon, 9 Oct 2023 15:16:50 +0200 Subject: [PATCH 0197/2851] uucore: remove commented out import --- src/uucore/src/lib/features/tokenize/sub.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/uucore/src/lib/features/tokenize/sub.rs b/src/uucore/src/lib/features/tokenize/sub.rs index 447616ae6a6..c65a37a689b 100644 --- a/src/uucore/src/lib/features/tokenize/sub.rs +++ b/src/uucore/src/lib/features/tokenize/sub.rs @@ -18,7 +18,6 @@ use std::iter::Peekable; use std::process::exit; use std::slice::Iter; use std::str::Chars; -// use std::collections::HashSet; use super::num_format::format_field::{FieldType, FormatField}; use super::num_format::num_format; From b38ac2cb7488e0006c5a98f87bc36b9ce07dc150 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Mon, 9 Oct 2023 15:28:12 +0200 Subject: [PATCH 0198/2851] uucore: remove commented out enum FChar --- .../tokenize/num_format/format_field.rs | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/src/uucore/src/lib/features/tokenize/num_format/format_field.rs b/src/uucore/src/lib/features/tokenize/num_format/format_field.rs index 036ee286d25..bd57b0ecdaf 100644 --- a/src/uucore/src/lib/features/tokenize/num_format/format_field.rs +++ b/src/uucore/src/lib/features/tokenize/num_format/format_field.rs @@ -17,23 +17,6 @@ pub enum FieldType { Charf, } -// #[allow(non_camel_case_types)] -// pub enum FChar { -// d, -// e, -// E, -// i, -// f, -// F, -// g, -// G, -// u, -// x, -// X, -// o -// } -// - // a Sub Tokens' fields are stored // as a single object so they can be more simply // passed by ref to num_format in a Sub method From d2cacdfce27adbf9c64b1cd4d72f05daf6d48f45 Mon Sep 17 00:00:00 2001 From: Howard Su Date: Mon, 9 Oct 2023 09:38:06 +0800 Subject: [PATCH 0199/2851] Fix overflow error on WSL. Default to 0 when the values are non-sense. --- src/uu/df/src/table.rs | 37 +++++++++++++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/src/uu/df/src/table.rs b/src/uu/df/src/table.rs index 4c3d08f4533..e1f7fd82ec3 100644 --- a/src/uu/df/src/table.rs +++ b/src/uu/df/src/table.rs @@ -152,8 +152,10 @@ impl From for Row { ffree, .. } = fs.usage; - let bused = blocks - bfree; - let fused = files - ffree; + + // On Windows WSL, files can be less than ffree. Protect such cases via saturating_sub. + let bused = blocks.saturating_sub(bfree); + let fused = files.saturating_sub(ffree); Self { file: fs.file, fs_device: dev_name, @@ -815,4 +817,35 @@ mod tests { assert_eq!(get_formatted_values(1000, 1000, 0), vec!("1", "1", "0")); assert_eq!(get_formatted_values(1001, 1000, 1), vec!("2", "1", "1")); } + + #[test] + fn test_row_converter_with_invalid_numbers() { + // copy from wsl linux + let d = crate::Filesystem { + file: None, + mount_info: crate::MountInfo { + dev_id: "28".to_string(), + dev_name: "none".to_string(), + fs_type: "9p".to_string(), + mount_dir: "/usr/lib/wsl/drivers".to_string(), + mount_option: "ro,nosuid,nodev,noatime".to_string(), + mount_root: "/".to_string(), + remote: false, + dummy: false, + }, + usage: crate::table::FsUsage { + blocksize: 4096, + blocks: 244029695, + bfree: 125085030, + bavail: 125085030, + bavail_top_bit_set: false, + files: 999, + ffree: 1000000, + }, + }; + + let row = Row::from(d); + + assert_eq!(row.inodes_used, 0); + } } From 536dbae90aed66f2a6261e84363342a94e4f59a7 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Tue, 10 Oct 2023 07:58:16 +0200 Subject: [PATCH 0200/2851] Bump bstr and regex bstr from 1.6 -> 1.7 regex from 1.9.6 -> 1.10.0 --- Cargo.lock | 16 ++++++++-------- Cargo.toml | 4 ++-- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d4da117364d..9d43cc65d1a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -188,9 +188,9 @@ dependencies = [ [[package]] name = "bstr" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6798148dccfbff0fae41c7574d2fa8f1ef3492fba0face179de5d8d447d67b05" +checksum = "c79ad7fb2dd38f3dabd76b09c6a5a20c038fc0213ef1e9afd30eb777f120f019" dependencies = [ "memchr", "regex-automata", @@ -1728,9 +1728,9 @@ checksum = "f1bfbf25d7eb88ddcbb1ec3d755d0634da8f7657b2cb8b74089121409ab8228f" [[package]] name = "regex" -version = "1.9.6" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebee201405406dbf528b8b672104ae6d6d63e6d118cb10e4d51abbc7b58044ff" +checksum = "d119d7c7ca818f8a53c300863d4f87566aac09943aef5b355bb83969dae75d87" dependencies = [ "aho-corasick", "memchr", @@ -1740,9 +1740,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.3.9" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59b23e92ee4318893fa3fe3e6fb365258efbfe6ac6ab30f090cdcbb7aa37efa9" +checksum = "465c6fc0621e4abc4187a2bda0937bfd4f722c2730b29562e19689ea796c9a4b" dependencies = [ "aho-corasick", "memchr", @@ -1751,9 +1751,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.7.5" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" +checksum = "c3cbb081b9784b07cceb8824c8583f86db4814d172ab043f3c23f7dc600bf83d" [[package]] name = "relative-path" diff --git a/Cargo.toml b/Cargo.toml index 8eecb2b8f65..df729a76521 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -259,7 +259,7 @@ test = ["uu_test"] [workspace.dependencies] bigdecimal = "0.4" binary-heap-plus = "0.5.0" -bstr = "1.6" +bstr = "1.7" bytecount = "0.6.4" byteorder = "1.5.0" chrono = { version = "^0.4.31", default-features = false, features = [ @@ -309,7 +309,7 @@ rand = { version = "0.8", features = ["small_rng"] } rand_core = "0.6" rayon = "1.8" redox_syscall = "0.4" -regex = "1.9.6" +regex = "1.10.0" rstest = "0.18.2" rust-ini = "0.19.0" same-file = "1.0.6" From 95a1a08de3d44240112fc1c3e959b268cf216678 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Tue, 10 Oct 2023 09:07:32 +0200 Subject: [PATCH 0201/2851] Add two words to the ignore spell list --- src/uu/df/src/table.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/df/src/table.rs b/src/uu/df/src/table.rs index e1f7fd82ec3..f6e09420482 100644 --- a/src/uu/df/src/table.rs +++ b/src/uu/df/src/table.rs @@ -2,7 +2,7 @@ // // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// spell-checker:ignore tmpfs Pcent Itotal Iused Iavail Ipcent +// spell-checker:ignore tmpfs Pcent Itotal Iused Iavail Ipcent nosuid nodev //! The filesystem usage data table. //! //! A table ([`Table`]) comprises a header row ([`Header`]) and a From 6cb0d5ad7dceca1193ded89d133047d773c19c4b Mon Sep 17 00:00:00 2001 From: tommady Date: Wed, 11 Oct 2023 07:53:03 +0000 Subject: [PATCH 0202/2851] add comment for explicit in the Preserve enum --- src/uu/cp/src/cp.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index 72431cc12b5..58c56138ec5 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -184,6 +184,8 @@ pub struct Attributes { #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Preserve { + // explicit means is the --no-preserve flag is used or not to distinguish out the default value. + // e.g. --no-preserve=mode means mode = No { explicit = true } No { explicit: bool }, Yes { required: bool }, } From 3b971e480624036b00aa5e9b5f3813dd5e6b71ba Mon Sep 17 00:00:00 2001 From: Miles Liu Date: Wed, 11 Oct 2023 16:26:36 +0800 Subject: [PATCH 0203/2851] ls: move to uutils-term-grid --- Cargo.lock | 20 ++++++++++---------- Cargo.toml | 2 +- src/uu/ls/Cargo.toml | 2 +- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9d43cc65d1a..a0a62d13ba6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2085,15 +2085,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "term_grid" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "230d3e804faaed5a39b08319efb797783df2fd9671b39b7596490cb486d702cf" -dependencies = [ - "unicode-width", -] - [[package]] name = "terminal_size" version = "0.2.6" @@ -2649,10 +2640,10 @@ dependencies = [ "number_prefix", "once_cell", "selinux", - "term_grid", "terminal_size 0.3.0", "unicode-width", "uucore", + "uutils_term_grid", ] [[package]] @@ -3276,6 +3267,15 @@ version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "422ee0de9031b5b948b97a8fc04e3aa35230001a722ddd27943e0be31564ce4c" +[[package]] +name = "uutils_term_grid" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b389452a568698688dda38802068378a16c15c4af9b153cdd99b65391292bbc7" +dependencies = [ + "unicode-width", +] + [[package]] name = "version_check" version = "0.9.4" diff --git a/Cargo.toml b/Cargo.toml index df729a76521..820568e616e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -318,7 +318,7 @@ selinux = "0.4" signal-hook = "0.3.17" smallvec = { version = "1.11", features = ["union"] } tempfile = "3.8.0" -term_grid = "0.1.5" +uutils_term_grid = "0.3" terminal_size = "0.3.0" textwrap = { version = "0.16.0", features = ["terminal_size"] } thiserror = "1.0" diff --git a/src/uu/ls/Cargo.toml b/src/uu/ls/Cargo.toml index e4100d07f8b..4394865caba 100644 --- a/src/uu/ls/Cargo.toml +++ b/src/uu/ls/Cargo.toml @@ -19,7 +19,7 @@ clap = { workspace = true, features = ["env"] } chrono = { workspace = true } unicode-width = { workspace = true } number_prefix = { workspace = true } -term_grid = { workspace = true } +uutils_term_grid = { workspace = true } terminal_size = { workspace = true } glob = { workspace = true } lscolors = { workspace = true } From 78e1eae1effe8f60bfcbc49f70acbf8a5530f993 Mon Sep 17 00:00:00 2001 From: tommady Date: Wed, 11 Oct 2023 08:29:13 +0000 Subject: [PATCH 0204/2851] address comment --- src/uu/cp/src/cp.rs | 91 +++++++++++++++++++++++---------------------- 1 file changed, 47 insertions(+), 44 deletions(-) diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index 58c56138ec5..034cd4b12c7 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -1743,50 +1743,7 @@ fn copy_file( let mut permissions = source_metadata.permissions(); #[cfg(unix)] { - let mut mode = permissions.mode(); - - let (is_preserve_mode, is_explicit_no_preserve_mode) = options.preserve_mode(); - if !is_preserve_mode { - use libc::{ - S_IRGRP, S_IROTH, S_IRUSR, S_IRWXG, S_IRWXO, S_IRWXU, S_IWGRP, S_IWOTH, S_IWUSR, - }; - - #[cfg(not(any( - target_os = "android", - target_os = "macos", - target_os = "macos-12", - target_os = "freebsd", - )))] - const MODE_RW_UGO: u32 = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; - #[cfg(not(any( - target_os = "android", - target_os = "macos", - target_os = "macos-12", - target_os = "freebsd", - )))] - const S_IRWXUGO: u32 = S_IRWXU | S_IRWXG | S_IRWXO; - - #[cfg(any( - target_os = "android", - target_os = "macos", - target_os = "macos-12", - target_os = "freebsd", - ))] - const MODE_RW_UGO: u32 = - (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH) as u32; - #[cfg(any( - target_os = "android", - target_os = "macos", - target_os = "macos-12", - target_os = "freebsd", - ))] - const S_IRWXUGO: u32 = (S_IRWXU | S_IRWXG | S_IRWXO) as u32; - - match is_explicit_no_preserve_mode { - true => mode = MODE_RW_UGO, - false => mode &= S_IRWXUGO, - } - } + let mut mode = handling_no_preserve_mode(options, permissions.mode()); // apply umask use uucore::mode::get_umask; @@ -1918,6 +1875,52 @@ fn copy_file( Ok(()) } +fn handling_no_preserve_mode(options: &Options, org_mode: u32) -> u32 { + let (is_preserve_mode, is_explicit_no_preserve_mode) = options.preserve_mode(); + if !is_preserve_mode { + use libc::{ + S_IRGRP, S_IROTH, S_IRUSR, S_IRWXG, S_IRWXO, S_IRWXU, S_IWGRP, S_IWOTH, S_IWUSR, + }; + + #[cfg(not(any( + target_os = "android", + target_os = "macos", + target_os = "macos-12", + target_os = "freebsd", + )))] + const MODE_RW_UGO: u32 = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; + #[cfg(not(any( + target_os = "android", + target_os = "macos", + target_os = "macos-12", + target_os = "freebsd", + )))] + const S_IRWXUGO: u32 = S_IRWXU | S_IRWXG | S_IRWXO; + + #[cfg(any( + target_os = "android", + target_os = "macos", + target_os = "macos-12", + target_os = "freebsd", + ))] + const MODE_RW_UGO: u32 = (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH) as u32; + #[cfg(any( + target_os = "android", + target_os = "macos", + target_os = "macos-12", + target_os = "freebsd", + ))] + const S_IRWXUGO: u32 = (S_IRWXU | S_IRWXG | S_IRWXO) as u32; + + match is_explicit_no_preserve_mode { + true => return MODE_RW_UGO, + false => return org_mode & S_IRWXUGO, + }; + } + + org_mode +} + /// Copy the file from `source` to `dest` either using the normal `fs::copy` or a /// copy-on-write scheme if --reflink is specified and the filesystem supports it. fn copy_helper( From c59b0f11fd821254863930ff51328a5bfbeee45f Mon Sep 17 00:00:00 2001 From: tommady Date: Wed, 11 Oct 2023 08:57:04 +0000 Subject: [PATCH 0205/2851] fix linter --- src/uu/cp/src/cp.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index 034cd4b12c7..b1dd94dbd56 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -1875,6 +1875,7 @@ fn copy_file( Ok(()) } +#[cfg(unix)] fn handling_no_preserve_mode(options: &Options, org_mode: u32) -> u32 { let (is_preserve_mode, is_explicit_no_preserve_mode) = options.preserve_mode(); if !is_preserve_mode { From a920464952df36e6c1797bc5dccf29f2976edf83 Mon Sep 17 00:00:00 2001 From: zhitkoff Date: Wed, 11 Oct 2023 12:13:22 -0400 Subject: [PATCH 0206/2851] split: undocumented options aliases + help fix --- src/uu/split/src/split.rs | 4 +++- tests/by-util/test_split.rs | 26 ++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/src/uu/split/src/split.rs b/src/uu/split/src/split.rs index bfd595e4f86..020ada93bb5 100644 --- a/src/uu/split/src/split.rs +++ b/src/uu/split/src/split.rs @@ -311,6 +311,7 @@ pub fn uu_app() -> Command { .arg( Arg::new(OPT_NUMERIC_SUFFIXES) .long(OPT_NUMERIC_SUFFIXES) + .alias("numeric") .require_equals(true) .default_missing_value("0") .num_args(0..=1) @@ -338,6 +339,7 @@ pub fn uu_app() -> Command { .arg( Arg::new(OPT_HEX_SUFFIXES) .long(OPT_HEX_SUFFIXES) + .alias("hex") .default_missing_value("0") .require_equals(true) .num_args(0..=1) @@ -372,7 +374,7 @@ pub fn uu_app() -> Command { .allow_hyphen_values(true) .value_name("SEP") .action(ArgAction::Append) - .help("use SEP instead of newline as the record separator; '\0' (zero) specifies the NUL character"), + .help("use SEP instead of newline as the record separator; '\\0' (zero) specifies the NUL character"), ) .arg( Arg::new(OPT_IO) diff --git a/tests/by-util/test_split.rs b/tests/by-util/test_split.rs index ce80844cf3e..113c0fb87ff 100644 --- a/tests/by-util/test_split.rs +++ b/tests/by-util/test_split.rs @@ -1192,6 +1192,19 @@ fn test_numeric_suffix() { assert_eq!(at.read("x12"), ""); } +#[test] +fn test_numeric_suffix_alias() { + let (at, mut ucmd) = at_and_ucmd!(); + ucmd.args(&["-n", "4", "--numeric=9", "threebytes.txt"]) + .succeeds() + .no_stdout() + .no_stderr(); + assert_eq!(at.read("x09"), "a"); + assert_eq!(at.read("x10"), "b"); + assert_eq!(at.read("x11"), "c"); + assert_eq!(at.read("x12"), ""); +} + #[test] fn test_hex_suffix() { let (at, mut ucmd) = at_and_ucmd!(); @@ -1205,6 +1218,19 @@ fn test_hex_suffix() { assert_eq!(at.read("x0c"), ""); } +#[test] +fn test_hex_suffix_alias() { + let (at, mut ucmd) = at_and_ucmd!(); + ucmd.args(&["-n", "4", "--hex=9", "threebytes.txt"]) + .succeeds() + .no_stdout() + .no_stderr(); + assert_eq!(at.read("x09"), "a"); + assert_eq!(at.read("x0a"), "b"); + assert_eq!(at.read("x0b"), "c"); + assert_eq!(at.read("x0c"), ""); +} + #[test] fn test_numeric_suffix_no_equal() { new_ucmd!() From 7ea19c7c5dd2f99d8fc4db3e2337f7a9072b715b Mon Sep 17 00:00:00 2001 From: tommady Date: Thu, 12 Oct 2023 10:42:44 +0800 Subject: [PATCH 0207/2851] Update src/uu/cp/src/cp.rs Co-authored-by: Sylvestre Ledru --- src/uu/cp/src/cp.rs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index b1dd94dbd56..eb14e09048a 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -1889,14 +1889,10 @@ fn handling_no_preserve_mode(options: &Options, org_mode: u32) -> u32 { target_os = "macos-12", target_os = "freebsd", )))] - const MODE_RW_UGO: u32 = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; - #[cfg(not(any( - target_os = "android", - target_os = "macos", - target_os = "macos-12", - target_os = "freebsd", - )))] - const S_IRWXUGO: u32 = S_IRWXU | S_IRWXG | S_IRWXO; + { + const MODE_RW_UGO: u32 = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; + const S_IRWXUGO: u32 = S_IRWXU | S_IRWXG | S_IRWXO; + } #[cfg(any( target_os = "android", From 9be5583d363972b05c07d6cae95c549665edb519 Mon Sep 17 00:00:00 2001 From: tommady Date: Thu, 12 Oct 2023 10:42:52 +0800 Subject: [PATCH 0208/2851] Update src/uu/cp/src/cp.rs Co-authored-by: Sylvestre Ledru --- src/uu/cp/src/cp.rs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index eb14e09048a..5df5eaf20b1 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -1900,14 +1900,10 @@ fn handling_no_preserve_mode(options: &Options, org_mode: u32) -> u32 { target_os = "macos-12", target_os = "freebsd", ))] - const MODE_RW_UGO: u32 = (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH) as u32; - #[cfg(any( - target_os = "android", - target_os = "macos", - target_os = "macos-12", - target_os = "freebsd", - ))] - const S_IRWXUGO: u32 = (S_IRWXU | S_IRWXG | S_IRWXO) as u32; + { + const MODE_RW_UGO: u32 = (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH) as u32; + const S_IRWXUGO: u32 = (S_IRWXU | S_IRWXG | S_IRWXO) as u32; + } match is_explicit_no_preserve_mode { true => return MODE_RW_UGO, From c9fa07035b1846fedd3883bdf04dddd7ea8c5e6f Mon Sep 17 00:00:00 2001 From: tommady Date: Thu, 12 Oct 2023 02:52:02 +0000 Subject: [PATCH 0209/2851] fix linter errors --- src/uu/cp/src/cp.rs | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index 5df5eaf20b1..40b485c5c6b 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -1890,8 +1890,12 @@ fn handling_no_preserve_mode(options: &Options, org_mode: u32) -> u32 { target_os = "freebsd", )))] { - const MODE_RW_UGO: u32 = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; + const MODE_RW_UGO: u32 = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; const S_IRWXUGO: u32 = S_IRWXU | S_IRWXG | S_IRWXO; + match is_explicit_no_preserve_mode { + true => return MODE_RW_UGO, + false => return org_mode & S_IRWXUGO, + }; } #[cfg(any( @@ -1901,14 +1905,14 @@ fn handling_no_preserve_mode(options: &Options, org_mode: u32) -> u32 { target_os = "freebsd", ))] { - const MODE_RW_UGO: u32 = (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH) as u32; + const MODE_RW_UGO: u32 = + (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH) as u32; const S_IRWXUGO: u32 = (S_IRWXU | S_IRWXG | S_IRWXO) as u32; - } - - match is_explicit_no_preserve_mode { - true => return MODE_RW_UGO, - false => return org_mode & S_IRWXUGO, - }; + match is_explicit_no_preserve_mode { + true => return MODE_RW_UGO, + false => return org_mode & S_IRWXUGO, + }; + } } org_mode From 2bd5e263172772b595412fb978668709d2033230 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Thu, 12 Oct 2023 17:20:00 +0200 Subject: [PATCH 0210/2851] DEVELOPMENT.md: improve doc on how to run some tests --- DEVELOPMENT.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index 24a1bdeb52c..308daba9818 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -142,6 +142,8 @@ If you also want to test the core utilities: ```shell cargo test -p uucore -p coreutils +# or +cargo test --all-features -p uucore ``` Running the complete test suite might take a while. We use [nextest](https://nexte.st/index.html) in From 29a5a13ce6a153b15d6eba742dd7747b0abce824 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Thu, 12 Oct 2023 14:29:26 +0200 Subject: [PATCH 0211/2851] fs: split get_file_display into its function --- src/uucore/src/lib/features/fs.rs | 54 ++++++++++++++++++++++++------- 1 file changed, 43 insertions(+), 11 deletions(-) diff --git a/src/uucore/src/lib/features/fs.rs b/src/uucore/src/lib/features/fs.rs index 719efc7a6e7..97238b10df4 100644 --- a/src/uucore/src/lib/features/fs.rs +++ b/src/uucore/src/lib/features/fs.rs @@ -465,6 +465,35 @@ pub fn display_permissions(metadata: &fs::Metadata, display_file_type: bool) -> display_permissions_unix(mode, display_file_type) } +/// Returns a character representation of the file type based on its mode. +/// This function is specific to Unix-like systems. +/// +/// - `mode`: The mode of the file, typically obtained from file metadata. +/// +/// # Returns +/// - 'd' for directories +/// - 'c' for character devices +/// - 'b' for block devices +/// - '-' for regular files +/// - 'p' for FIFOs (named pipes) +/// - 'l' for symbolic links +/// - 's' for sockets +/// - '?' for any other unrecognized file types +#[cfg(unix)] +fn get_file_display(mode: mode_t) -> char { + match mode & S_IFMT { + S_IFDIR => 'd', + S_IFCHR => 'c', + S_IFBLK => 'b', + S_IFREG => '-', + S_IFIFO => 'p', + S_IFLNK => 'l', + S_IFSOCK => 's', + // TODO: Other file types + _ => '?', + } +} + // The logic below is more readable written this way. #[allow(clippy::if_not_else)] #[allow(clippy::cognitive_complexity)] @@ -474,17 +503,7 @@ pub fn display_permissions_unix(mode: mode_t, display_file_type: bool) -> String let mut result; if display_file_type { result = String::with_capacity(10); - result.push(match mode & S_IFMT { - S_IFDIR => 'd', - S_IFCHR => 'c', - S_IFBLK => 'b', - S_IFREG => '-', - S_IFIFO => 'p', - S_IFLNK => 'l', - S_IFSOCK => 's', - // TODO: Other file types - _ => '?', - }); + result.push(get_file_display(mode)); } else { result = String::with_capacity(9); } @@ -881,4 +900,17 @@ mod tests { assert!(are_hardlinks_to_same_file(&path1, &path2)); } + + #[cfg(unix)] + #[test] + fn test_get_file_display() { + assert_eq!(get_file_display(S_IFDIR | 0o755), 'd'); + assert_eq!(get_file_display(S_IFCHR | 0o644), 'c'); + assert_eq!(get_file_display(S_IFBLK | 0o600), 'b'); + assert_eq!(get_file_display(S_IFREG | 0o777), '-'); + assert_eq!(get_file_display(S_IFIFO | 0o666), 'p'); + assert_eq!(get_file_display(S_IFLNK | 0o777), 'l'); + assert_eq!(get_file_display(S_IFSOCK | 0o600), 's'); + assert_eq!(get_file_display(0o777), '?'); + } } From 94972d45c7aa0532c3b44d2c131b4028b63f5555 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Thu, 12 Oct 2023 23:07:27 +0200 Subject: [PATCH 0212/2851] ls: Document a bit tests/ls/stat-dtype.sh --- src/uu/ls/src/ls.rs | 8 ++++---- tests/by-util/test_ls.rs | 14 ++++++++++++++ 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 39bab79823f..8a05e81f89d 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -2395,10 +2395,10 @@ fn display_grid( writeln!(out)?; } } else { - let mut grid = Grid::new(GridOptions { - filling: Filling::Spaces(2), - direction, - }); + // To match gnu/tests/ls/stat-dtype.sh + // we might want to have Text("\t".to_string()); + let filling = Filling::Spaces(2); + let mut grid = Grid::new(GridOptions { filling, direction }); for name in names { grid.add(name); diff --git a/tests/by-util/test_ls.rs b/tests/by-util/test_ls.rs index 7d0f86298cd..d9c1c8740e9 100644 --- a/tests/by-util/test_ls.rs +++ b/tests/by-util/test_ls.rs @@ -3678,3 +3678,17 @@ fn test_ls_dired_complex() { println!("Extracted filenames: {:?}", filenames); assert_eq!(filenames, vec!["a1", "a22", "a333", "a4444", "d"]); } + +#[ignore = "issue #5396"] +#[test] +fn test_ls_tabsize_cf() { + let (at, mut ucmd) = at_and_ucmd!(); + + at.mkdir("e"); + at.mkdir("e/a2345"); + at.mkdir("e/b"); + + ucmd.args(&["-CF", "e"]) + .succeeds() + .stdout_is("a2345/\tb/\n"); +} From cd51eb8eb58ca8c893ba775707537bc475e54e4a Mon Sep 17 00:00:00 2001 From: Miles Liu Date: Fri, 13 Oct 2023 14:13:10 +0800 Subject: [PATCH 0213/2851] ci: code-quality workflow doesn't fail on clippy errors --- .github/workflows/code-quality.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml index abcfd90ab96..0a619e09773 100644 --- a/.github/workflows/code-quality.yml +++ b/.github/workflows/code-quality.yml @@ -4,6 +4,10 @@ name: Code Quality on: [push, pull_request] +env: + # * style job configuration + STYLE_FAIL_ON_FAULT: true ## (bool) fail the build if a style job contains a fault (error or warning); may be overridden on a per-job basis + permissions: contents: read # to fetch code (actions/checkout) From f1f4823feb91dd3a8644287a1995e5f5433b8ef7 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Fri, 13 Oct 2023 08:26:06 +0200 Subject: [PATCH 0214/2851] who: suppress cognitive_complexity lint --- src/uu/who/src/who.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/uu/who/src/who.rs b/src/uu/who/src/who.rs index 29929b13866..5d952efffb2 100644 --- a/src/uu/who/src/who.rs +++ b/src/uu/who/src/who.rs @@ -326,6 +326,7 @@ fn current_tty() -> String { } impl Who { + #[allow(clippy::cognitive_complexity)] fn exec(&mut self) -> UResult<()> { let run_level_chk = |_record: i16| { #[cfg(not(target_os = "linux"))] From 8826e47d36086737370cb11d647b1399ab6d0b01 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Fri, 13 Oct 2023 10:30:31 +0200 Subject: [PATCH 0215/2851] ignore dtype in the spell --- src/uu/ls/src/ls.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 8a05e81f89d..bff7c446050 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -3,7 +3,7 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// spell-checker:ignore (ToDO) cpio svgz webm somegroup nlink rmvb xspf tabsize dired subdired +// spell-checker:ignore (ToDO) cpio svgz webm somegroup nlink rmvb xspf tabsize dired subdired dtype use clap::{ builder::{NonEmptyStringValueParser, ValueParser}, From 8931cfa93df1cd003ce081b413524fabbe8537f1 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Fri, 13 Oct 2023 11:18:00 +0200 Subject: [PATCH 0216/2851] improve the wordin Co-authored-by: Daniel Hofstetter --- src/uu/ls/src/ls.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index bff7c446050..dfa637d2300 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -2395,8 +2395,8 @@ fn display_grid( writeln!(out)?; } } else { - // To match gnu/tests/ls/stat-dtype.sh - // we might want to have Text("\t".to_string()); + // TODO: To match gnu/tests/ls/stat-dtype.sh + // we might want to have Filling::Text("\t".to_string()); let filling = Filling::Spaces(2); let mut grid = Grid::new(GridOptions { filling, direction }); From 41188a915ee33195f45ff86ac253cc0ef11c240c Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Fri, 13 Oct 2023 11:35:45 +0200 Subject: [PATCH 0217/2851] rename the test Co-authored-by: Daniel Hofstetter --- tests/by-util/test_ls.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/by-util/test_ls.rs b/tests/by-util/test_ls.rs index d9c1c8740e9..8b0032065d1 100644 --- a/tests/by-util/test_ls.rs +++ b/tests/by-util/test_ls.rs @@ -3681,7 +3681,7 @@ fn test_ls_dired_complex() { #[ignore = "issue #5396"] #[test] -fn test_ls_tabsize_cf() { +fn test_ls_cf_output_should_be_delimited_by_tab() { let (at, mut ucmd) = at_and_ucmd!(); at.mkdir("e"); From a69d48fb8c1d5b907a5943c99156352c13858316 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Fri, 13 Oct 2023 14:34:54 +0200 Subject: [PATCH 0218/2851] clippy: suppress cognitive_complexity lint --- src/uu/csplit/src/csplit.rs | 2 ++ src/uu/csplit/src/patterns.rs | 2 ++ src/uu/dd/src/numbers.rs | 1 + src/uu/dd/src/parseargs/unit_tests.rs | 1 + src/uu/df/src/blocks.rs | 1 + src/uu/head/src/head.rs | 1 + src/uu/head/src/parse.rs | 1 + src/uu/nl/src/nl.rs | 1 + src/uu/numfmt/src/format.rs | 1 + src/uu/numfmt/src/options.rs | 1 + src/uu/od/src/prn_float.rs | 1 + src/uu/seq/src/numberparse.rs | 2 ++ src/uu/split/src/number.rs | 4 ++++ src/uu/split/src/split.rs | 1 + src/uu/stat/src/stat.rs | 1 + 15 files changed, 21 insertions(+) diff --git a/src/uu/csplit/src/csplit.rs b/src/uu/csplit/src/csplit.rs index 6c9a776c388..6e03c2e5c83 100644 --- a/src/uu/csplit/src/csplit.rs +++ b/src/uu/csplit/src/csplit.rs @@ -664,6 +664,7 @@ mod tests { use super::*; #[test] + #[allow(clippy::cognitive_complexity)] fn input_splitter() { let input = vec![ Ok(String::from("aaa")), @@ -736,6 +737,7 @@ mod tests { } #[test] + #[allow(clippy::cognitive_complexity)] fn input_splitter_interrupt_rewind() { let input = vec![ Ok(String::from("aaa")), diff --git a/src/uu/csplit/src/patterns.rs b/src/uu/csplit/src/patterns.rs index fd96fd9fb9e..8e7b76e6bb4 100644 --- a/src/uu/csplit/src/patterns.rs +++ b/src/uu/csplit/src/patterns.rs @@ -211,6 +211,7 @@ mod tests { } #[test] + #[allow(clippy::cognitive_complexity)] fn up_to_match_pattern() { let input: Vec = vec![ "/test1.*end$/", @@ -264,6 +265,7 @@ mod tests { } #[test] + #[allow(clippy::cognitive_complexity)] fn skip_to_match_pattern() { let input: Vec = vec![ "%test1.*end$%", diff --git a/src/uu/dd/src/numbers.rs b/src/uu/dd/src/numbers.rs index 2911f7e58c5..8a6fa5a7a37 100644 --- a/src/uu/dd/src/numbers.rs +++ b/src/uu/dd/src/numbers.rs @@ -115,6 +115,7 @@ mod tests { } #[test] + #[allow(clippy::cognitive_complexity)] fn test_to_magnitude_and_suffix_not_powers_of_1024() { assert_eq!(to_magnitude_and_suffix(1, SuffixType::Si), "1.0 B"); assert_eq!(to_magnitude_and_suffix(999, SuffixType::Si), "999 B"); diff --git a/src/uu/dd/src/parseargs/unit_tests.rs b/src/uu/dd/src/parseargs/unit_tests.rs index a190fd75bd2..142e49fd0ba 100644 --- a/src/uu/dd/src/parseargs/unit_tests.rs +++ b/src/uu/dd/src/parseargs/unit_tests.rs @@ -103,6 +103,7 @@ fn test_status_level_none() { } #[test] +#[allow(clippy::cognitive_complexity)] fn test_all_top_level_args_no_leading_dashes() { let args = &[ "if=foo.file", diff --git a/src/uu/df/src/blocks.rs b/src/uu/df/src/blocks.rs index 9bc16b7820a..fad8f7ac0c4 100644 --- a/src/uu/df/src/blocks.rs +++ b/src/uu/df/src/blocks.rs @@ -239,6 +239,7 @@ mod tests { } #[test] + #[allow(clippy::cognitive_complexity)] fn test_to_magnitude_and_suffix_not_powers_of_1024() { assert_eq!(to_magnitude_and_suffix(1, SuffixType::Si), "1B"); assert_eq!(to_magnitude_and_suffix(999, SuffixType::Si), "999B"); diff --git a/src/uu/head/src/head.rs b/src/uu/head/src/head.rs index d6e4db5a78e..c533f5a5df0 100644 --- a/src/uu/head/src/head.rs +++ b/src/uu/head/src/head.rs @@ -548,6 +548,7 @@ mod tests { } #[test] + #[allow(clippy::cognitive_complexity)] fn all_args_test() { assert!(options("--silent").unwrap().quiet); assert!(options("--quiet").unwrap().quiet); diff --git a/src/uu/head/src/parse.rs b/src/uu/head/src/parse.rs index 7f7dd48f8c5..062a1844ce8 100644 --- a/src/uu/head/src/parse.rs +++ b/src/uu/head/src/parse.rs @@ -153,6 +153,7 @@ mod tests { } #[test] + #[allow(clippy::cognitive_complexity)] fn test_parse_numbers_obsolete() { assert_eq!(obsolete("-5"), obsolete_result(&["-n", "5"])); assert_eq!(obsolete("-100"), obsolete_result(&["-n", "100"])); diff --git a/src/uu/nl/src/nl.rs b/src/uu/nl/src/nl.rs index 71b4aac2886..61ca8406f1e 100644 --- a/src/uu/nl/src/nl.rs +++ b/src/uu/nl/src/nl.rs @@ -394,6 +394,7 @@ mod test { use super::*; #[test] + #[allow(clippy::cognitive_complexity)] fn test_format() { assert_eq!(NumberFormat::Left.format(12, 1), "12"); assert_eq!(NumberFormat::Left.format(-12, 1), "-12"); diff --git a/src/uu/numfmt/src/format.rs b/src/uu/numfmt/src/format.rs index 08bb0c2e774..034d900e92d 100644 --- a/src/uu/numfmt/src/format.rs +++ b/src/uu/numfmt/src/format.rs @@ -430,6 +430,7 @@ mod tests { use super::*; #[test] + #[allow(clippy::cognitive_complexity)] fn test_round_with_precision() { let rm = RoundMethod::FromZero; assert_eq!(1.0, round_with_precision(0.12345, rm, 0)); diff --git a/src/uu/numfmt/src/options.rs b/src/uu/numfmt/src/options.rs index 07b364f18d8..88e64e963e3 100644 --- a/src/uu/numfmt/src/options.rs +++ b/src/uu/numfmt/src/options.rs @@ -266,6 +266,7 @@ mod tests { } #[test] + #[allow(clippy::cognitive_complexity)] fn test_parse_format_with_invalid_formats() { assert!("".parse::().is_err()); assert!("hello".parse::().is_err()); diff --git a/src/uu/od/src/prn_float.rs b/src/uu/od/src/prn_float.rs index f4d018bd84f..f44abf7c41c 100644 --- a/src/uu/od/src/prn_float.rs +++ b/src/uu/od/src/prn_float.rs @@ -198,6 +198,7 @@ fn test_format_flo64() { } #[test] +#[allow(clippy::cognitive_complexity)] fn test_format_flo16() { assert_eq!(format_flo16(f16::from_bits(0x8400u16)), "-6.104e-5"); assert_eq!(format_flo16(f16::from_bits(0x8401u16)), "-6.109e-5"); diff --git a/src/uu/seq/src/numberparse.rs b/src/uu/seq/src/numberparse.rs index 46991725547..3f4b213955f 100644 --- a/src/uu/seq/src/numberparse.rs +++ b/src/uu/seq/src/numberparse.rs @@ -538,6 +538,7 @@ mod tests { } #[test] + #[allow(clippy::cognitive_complexity)] fn test_num_integral_digits() { // no decimal, no exponent assert_eq!(num_integral_digits("123"), 3); @@ -578,6 +579,7 @@ mod tests { } #[test] + #[allow(clippy::cognitive_complexity)] fn test_num_fractional_digits() { // no decimal, no exponent assert_eq!(num_fractional_digits("123"), 0); diff --git a/src/uu/split/src/number.rs b/src/uu/split/src/number.rs index 39d64f9272e..a01701c80e3 100644 --- a/src/uu/split/src/number.rs +++ b/src/uu/split/src/number.rs @@ -398,6 +398,7 @@ mod tests { } #[test] + #[allow(clippy::cognitive_complexity)] fn test_dynamic_width_number_display_alphabetic() { fn num(n: usize) -> Number { let mut number = Number::DynamicWidth(DynamicWidthNumber::new(26, 0)); @@ -443,6 +444,7 @@ mod tests { } #[test] + #[allow(clippy::cognitive_complexity)] fn test_dynamic_width_number_display_numeric_hexadecimal() { fn num(n: usize) -> Number { let mut number = Number::DynamicWidth(DynamicWidthNumber::new(16, 0)); @@ -467,6 +469,7 @@ mod tests { } #[test] + #[allow(clippy::cognitive_complexity)] fn test_fixed_width_number_increment() { let mut n = Number::FixedWidth(FixedWidthNumber::new(3, 2, 0).unwrap()); assert_eq!(n.digits(), vec![0, 0]); @@ -490,6 +493,7 @@ mod tests { } #[test] + #[allow(clippy::cognitive_complexity)] fn test_fixed_width_number_display_alphabetic() { fn num(n: usize) -> Result { let mut number = Number::FixedWidth(FixedWidthNumber::new(26, 2, 0).unwrap()); diff --git a/src/uu/split/src/split.rs b/src/uu/split/src/split.rs index 020ada93bb5..84b5900cc2c 100644 --- a/src/uu/split/src/split.rs +++ b/src/uu/split/src/split.rs @@ -1846,6 +1846,7 @@ mod tests { } #[test] + #[allow(clippy::cognitive_complexity)] fn test_number_type_from_error() { assert_eq!( NumberType::from("xyz").unwrap_err(), diff --git a/src/uu/stat/src/stat.rs b/src/uu/stat/src/stat.rs index 055393578e2..7d1fd574c25 100644 --- a/src/uu/stat/src/stat.rs +++ b/src/uu/stat/src/stat.rs @@ -947,6 +947,7 @@ mod tests { } #[test] + #[allow(clippy::cognitive_complexity)] fn test_group_num() { assert_eq!("12,379,821,234", group_num("12379821234")); assert_eq!("21,234", group_num("21234")); From ae1c4ccfd21e5b1c8541462c8afeac5c0f5fde05 Mon Sep 17 00:00:00 2001 From: Zhuoxun Yang Date: Sat, 14 Oct 2023 01:56:38 +0800 Subject: [PATCH 0219/2851] expr: short-circuit evaluation for `&` --- src/uu/expr/src/syntax_tree.rs | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/src/uu/expr/src/syntax_tree.rs b/src/uu/expr/src/syntax_tree.rs index 4ca723a4d8c..e0e786b3a3b 100644 --- a/src/uu/expr/src/syntax_tree.rs +++ b/src/uu/expr/src/syntax_tree.rs @@ -166,16 +166,26 @@ impl AstNode { { let mut out = Vec::with_capacity(operands.len()); let mut operands = operands.iter(); - // check the first value before `|`, stop evaluate and return directly if it is true. - // push dummy to pass the check of `len() == 2` - if op_type == "|" { - if let Some(value) = operands.next() { - let value = value.evaluate()?; - out.push(value.clone()); - if value_as_bool(&value) { - out.push(String::from("dummy")); - return Ok(out); + + if let Some(value) = operands.next() { + let value = value.evaluate()?; + out.push(value.clone()); + // short-circuit evaluation for `|` and `&` + // push dummy to pass `assert!(values.len() == 2);` + match op_type.as_ref() { + "|" => { + if value_as_bool(&value) { + out.push(String::from("dummy")); + return Ok(out); + } + } + "&" => { + if !value_as_bool(&value) { + out.push(String::from("dummy")); + return Ok(out); + } } + _ => {} } } From 40f05a331e26a6f27a86dfd13cd1eb8095b7b549 Mon Sep 17 00:00:00 2001 From: Zhuoxun Yang Date: Sat, 14 Oct 2023 01:58:53 +0800 Subject: [PATCH 0220/2851] tests/expr: add tests for test_and --- tests/by-util/test_expr.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/by-util/test_expr.rs b/tests/by-util/test_expr.rs index 4637d51c739..1064ef525ee 100644 --- a/tests/by-util/test_expr.rs +++ b/tests/by-util/test_expr.rs @@ -155,6 +155,21 @@ fn test_and() { .args(&["-14", "&", "1"]) .run() .stdout_is("-14\n"); + + new_ucmd!() + .args(&["0", "&", "a", "/", "5"]) + .run() + .stdout_only("0\n"); + + new_ucmd!() + .args(&["", "&", "a", "/", "5"]) + .run() + .stdout_only("0\n"); + + new_ucmd!() + .args(&["-1", "&", "10", "/", "5"]) + .succeeds() + .stdout_only("-1\n"); } #[test] From f979f148c11780a28fc23aafc2c68feab20b340a Mon Sep 17 00:00:00 2001 From: tommady Date: Sat, 14 Oct 2023 13:33:43 +0800 Subject: [PATCH 0221/2851] fuzz: store the corpus using GitHub Cache (#5363) --- .github/workflows/CICD.yml | 1 - .github/workflows/fuzzing.yml | 26 +++++++++++++------------- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index b971b0e00f1..6583a009474 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -1012,4 +1012,3 @@ jobs: flags: ${{ steps.vars.outputs.CODECOV_FLAGS }} name: codecov-umbrella fail_ci_if_error: false - diff --git a/.github/workflows/fuzzing.yml b/.github/workflows/fuzzing.yml index 677df2c9f18..311d6a0d7ac 100644 --- a/.github/workflows/fuzzing.yml +++ b/.github/workflows/fuzzing.yml @@ -24,41 +24,41 @@ jobs: - name: Install `cargo-fuzz` run: cargo install cargo-fuzz - uses: Swatinem/rust-cache@v2 + - name: Restore Cached Corpus + uses: actions/cache/restore@v3 + with: + key: corpus-cache + path: | + fuzz/corpus - name: Run fuzz_date for XX seconds + continue-on-error: true shell: bash run: | - ## Run it - cd fuzz cargo +nightly fuzz run fuzz_date -- -max_total_time=${{ env.RUN_FOR }} -detect_leaks=0 - name: Run fuzz_test for XX seconds - continue-on-error: true shell: bash run: | - ## Run it - cd fuzz cargo +nightly fuzz run fuzz_test -- -max_total_time=${{ env.RUN_FOR }} -detect_leaks=0 - name: Run fuzz_expr for XX seconds continue-on-error: true shell: bash run: | - ## Run it - cd fuzz cargo +nightly fuzz run fuzz_expr -- -max_total_time=${{ env.RUN_FOR }} -detect_leaks=0 - name: Run fuzz_parse_glob for XX seconds shell: bash run: | - ## Run it - cd fuzz cargo +nightly fuzz run fuzz_parse_glob -- -max_total_time=${{ env.RUN_FOR }} -detect_leaks=0 - name: Run fuzz_parse_size for XX seconds shell: bash run: | - ## Run it - cd fuzz cargo +nightly fuzz run fuzz_parse_size -- -max_total_time=${{ env.RUN_FOR }} -detect_leaks=0 - name: Run fuzz_parse_time for XX seconds shell: bash run: | - ## Run it - cd fuzz cargo +nightly fuzz run fuzz_parse_time -- -max_total_time=${{ env.RUN_FOR }} -detect_leaks=0 + - name: Save Corpus Cache + uses: actions/cache/save@v3 + with: + key: corpus-cache + path: | + fuzz/corpus From 505ef714b9980707f118ec29c185a47e6c99360b Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sat, 14 Oct 2023 11:41:48 +0200 Subject: [PATCH 0222/2851] rm: In some cases, remove_dir is doing a better job than remove_dir_all use it when remove_dir_all failed GNU compatibility (rm/empty-inacc.sh) --- src/uu/rm/src/rm.rs | 22 ++++++++++++++-------- tests/by-util/test_rm.rs | 15 +++++++++++++++ 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/src/uu/rm/src/rm.rs b/src/uu/rm/src/rm.rs index 87767b904bd..29ebd475809 100644 --- a/src/uu/rm/src/rm.rs +++ b/src/uu/rm/src/rm.rs @@ -330,14 +330,20 @@ fn handle_dir(path: &Path, options: &Options) -> bool { if options.recursive && (!is_root || !options.preserve_root) { if options.interactive != InteractiveMode::Always && !options.verbose { if let Err(e) = fs::remove_dir_all(path) { - had_err = true; - if e.kind() == std::io::ErrorKind::PermissionDenied { - // GNU compatibility (rm/fail-eacces.sh) - // here, GNU doesn't use some kind of remove_dir_all - // It will show directory+file - show_error!("cannot remove {}: {}", path.quote(), "Permission denied"); - } else { - show_error!("cannot remove {}: {}", path.quote(), e); + // GNU compatibility (rm/empty-inacc.sh) + // remove_dir_all failed. maybe it is because of the permissions + // but if the directory is empty, remove_dir might work. + // So, let's try that before failing for real + if let Err(_e) = fs::remove_dir(path) { + had_err = true; + if e.kind() == std::io::ErrorKind::PermissionDenied { + // GNU compatibility (rm/fail-eacces.sh) + // here, GNU doesn't use some kind of remove_dir_all + // It will show directory+file + show_error!("cannot remove {}: {}", path.quote(), "Permission denied"); + } else { + show_error!("cannot remove {}: {}", path.quote(), e); + } } } } else { diff --git a/tests/by-util/test_rm.rs b/tests/by-util/test_rm.rs index 73f99566c40..5125c746da7 100644 --- a/tests/by-util/test_rm.rs +++ b/tests/by-util/test_rm.rs @@ -647,6 +647,21 @@ fn test_prompt_write_protected_no() { assert!(at.file_exists(file_2)); } +#[cfg(feature = "chmod")] +#[test] +fn test_remove_inaccessible_dir() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let dir_1 = "test_rm_protected"; + + at.mkdir(dir_1); + + scene.ccmd("chmod").arg("0").arg(dir_1).succeeds(); + + scene.ucmd().arg("-rf").arg(dir_1).succeeds(); + assert!(!at.dir_exists(dir_1)); +} + #[test] #[cfg(not(windows))] fn test_fifo_removal() { From f557c5936430de6b864e8b1f120a41b160d65f74 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sat, 14 Oct 2023 11:43:07 +0200 Subject: [PATCH 0223/2851] doc: recommend the rust-gdb helper --- DEVELOPMENT.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index 308daba9818..9cb81a88ce4 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -157,7 +157,7 @@ cargo nextest run --features unix --no-fail-fast To debug: ```shell -gdb --args target/debug/coreutils ls +rust-gdb --args target/debug/coreutils ls (gdb) b ls.rs:79 (gdb) run ``` From 226680aa001457140985412adbdd5778d3dddc6e Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sat, 14 Oct 2023 13:45:29 +0200 Subject: [PATCH 0224/2851] Ignore inacc spell --- src/uu/rm/src/rm.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/rm/src/rm.rs b/src/uu/rm/src/rm.rs index 29ebd475809..cb88b6a2b98 100644 --- a/src/uu/rm/src/rm.rs +++ b/src/uu/rm/src/rm.rs @@ -3,7 +3,7 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// spell-checker:ignore (path) eacces +// spell-checker:ignore (path) eacces inacc use clap::{builder::ValueParser, crate_version, parser::ValueSource, Arg, ArgAction, Command}; use std::collections::VecDeque; From f6880bff8f5faca21a2e420261d0aac258ac9eb7 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Sat, 14 Oct 2023 14:58:41 +0200 Subject: [PATCH 0225/2851] expr: test some invalid syntaxes --- tests/by-util/test_expr.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/by-util/test_expr.rs b/tests/by-util/test_expr.rs index 1064ef525ee..3f403c7ea37 100644 --- a/tests/by-util/test_expr.rs +++ b/tests/by-util/test_expr.rs @@ -275,3 +275,16 @@ fn test_invalid_substr() { .code_is(1) .stdout_only("\n"); } + +#[test] +fn test_invalid_syntax() { + let invalid_syntaxes = [["12", "12"], ["12", "|"], ["|", "12"]]; + + for invalid_syntax in invalid_syntaxes { + new_ucmd!() + .args(&invalid_syntax) + .fails() + .code_is(2) + .stderr_contains("syntax error"); + } +} From 5b1755387f85217680831e582b97fb62d002db0c Mon Sep 17 00:00:00 2001 From: Zhuoxun Yang Date: Sat, 14 Oct 2023 23:18:15 +0800 Subject: [PATCH 0226/2851] tests/expr: test escape --- tests/by-util/test_expr.rs | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/tests/by-util/test_expr.rs b/tests/by-util/test_expr.rs index 1064ef525ee..b70c60fb37e 100644 --- a/tests/by-util/test_expr.rs +++ b/tests/by-util/test_expr.rs @@ -138,6 +138,11 @@ fn test_or() { .args(&["0", "|", "10", "/", "5"]) .succeeds() .stdout_only("2\n"); + + new_ucmd!() + .args(&["12", "|", "9a", "+", "1"]) + .succeeds() + .stdout_only("12\n"); } #[test] @@ -275,3 +280,23 @@ fn test_invalid_substr() { .code_is(1) .stdout_only("\n"); } + +#[test] +fn test_escape() { + new_ucmd!().args(&["+", "1"]).succeeds().stdout_only("1\n"); + + new_ucmd!() + .args(&["1", "+", "+", "1"]) + .succeeds() + .stdout_only("2\n"); + + new_ucmd!() + .args(&["2", "*", "+", "3"]) + .succeeds() + .stdout_only("6\n"); + + new_ucmd!() + .args(&["(", "1", ")", "+", "1"]) + .succeeds() + .stdout_only("2\n"); +} From 4f20773b4f78dcd1d8648bb93c11c5141b6f9095 Mon Sep 17 00:00:00 2001 From: Zhuoxun Yang Date: Sat, 14 Oct 2023 23:20:45 +0800 Subject: [PATCH 0227/2851] expr: fix escape --- src/uu/expr/src/tokens.rs | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/uu/expr/src/tokens.rs b/src/uu/expr/src/tokens.rs index 3c5cac060f9..8961935151b 100644 --- a/src/uu/expr/src/tokens.rs +++ b/src/uu/expr/src/tokens.rs @@ -16,8 +16,6 @@ // spell-checker:ignore (ToDO) paren -use num_bigint::BigInt; - #[derive(Debug, Clone)] pub enum Token { Value { @@ -59,11 +57,8 @@ impl Token { } } - fn is_a_number(&self) -> bool { - match self { - Self::Value { value, .. } => value.parse::().is_ok(), - _ => false, - } + fn is_a_value(&self) -> bool { + matches!(*self, Self::Value { .. }) } fn is_a_close_paren(&self) -> bool { @@ -131,14 +126,14 @@ fn maybe_dump_tokens_acc(tokens_acc: &[(usize, Token)]) { } fn push_token_if_not_escaped(acc: &mut Vec<(usize, Token)>, tok_idx: usize, token: Token, s: &str) { - // Smells heuristics... :( + // `+` may escaped such as `expr + 1` and `expr 1 + + 1` let prev_is_plus = match acc.last() { None => false, Some(t) => t.1.is_infix_plus(), }; let should_use_as_escaped = if prev_is_plus && acc.len() >= 2 { let pre_prev = &acc[acc.len() - 2]; - !(pre_prev.1.is_a_number() || pre_prev.1.is_a_close_paren()) + !(pre_prev.1.is_a_value() || pre_prev.1.is_a_close_paren()) } else { prev_is_plus }; From f89e6943b7ad7b00b4b0843cf756d3274b48357b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 14 Oct 2023 15:34:14 +0000 Subject: [PATCH 0228/2851] chore(deps): update rust crate regex to 1.10.1 --- Cargo.lock | 12 ++++++------ Cargo.toml | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a0a62d13ba6..f9be5346f61 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1728,9 +1728,9 @@ checksum = "f1bfbf25d7eb88ddcbb1ec3d755d0634da8f7657b2cb8b74089121409ab8228f" [[package]] name = "regex" -version = "1.10.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d119d7c7ca818f8a53c300863d4f87566aac09943aef5b355bb83969dae75d87" +checksum = "aaac441002f822bc9705a681810a4dd2963094b9ca0ddc41cb963a4c189189ea" dependencies = [ "aho-corasick", "memchr", @@ -1740,9 +1740,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "465c6fc0621e4abc4187a2bda0937bfd4f722c2730b29562e19689ea796c9a4b" +checksum = "5011c7e263a695dc8ca064cddb722af1be54e517a280b12a5356f98366899e5d" dependencies = [ "aho-corasick", "memchr", @@ -1751,9 +1751,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.0" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3cbb081b9784b07cceb8824c8583f86db4814d172ab043f3c23f7dc600bf83d" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" [[package]] name = "relative-path" diff --git a/Cargo.toml b/Cargo.toml index 820568e616e..975cced35bb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -309,7 +309,7 @@ rand = { version = "0.8", features = ["small_rng"] } rand_core = "0.6" rayon = "1.8" redox_syscall = "0.4" -regex = "1.10.0" +regex = "1.10.1" rstest = "0.18.2" rust-ini = "0.19.0" same-file = "1.0.6" From 6ef5b272da37e44c65ea69369f6b187fe871a177 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Sun, 15 Oct 2023 14:30:17 +0200 Subject: [PATCH 0229/2851] expr: add missing word to comment --- src/uu/expr/src/tokens.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/expr/src/tokens.rs b/src/uu/expr/src/tokens.rs index 8961935151b..f499881c138 100644 --- a/src/uu/expr/src/tokens.rs +++ b/src/uu/expr/src/tokens.rs @@ -126,7 +126,7 @@ fn maybe_dump_tokens_acc(tokens_acc: &[(usize, Token)]) { } fn push_token_if_not_escaped(acc: &mut Vec<(usize, Token)>, tok_idx: usize, token: Token, s: &str) { - // `+` may escaped such as `expr + 1` and `expr 1 + + 1` + // `+` may be escaped such as `expr + 1` and `expr 1 + + 1` let prev_is_plus = match acc.last() { None => false, Some(t) => t.1.is_infix_plus(), From e1bd47d5496b468607dfec1643b29fa2fbc8e0ee Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 15 Oct 2023 14:20:18 +0200 Subject: [PATCH 0230/2851] 0.0.21 => 0.0.22 --- Cargo.lock | 214 ++++++++++++------------- Cargo.toml | 206 ++++++++++++------------ src/uu/arch/Cargo.toml | 2 +- src/uu/base32/Cargo.toml | 2 +- src/uu/base64/Cargo.toml | 2 +- src/uu/basename/Cargo.toml | 2 +- src/uu/basenc/Cargo.toml | 2 +- src/uu/cat/Cargo.toml | 2 +- src/uu/chcon/Cargo.toml | 2 +- src/uu/chgrp/Cargo.toml | 2 +- src/uu/chmod/Cargo.toml | 2 +- src/uu/chown/Cargo.toml | 2 +- src/uu/chroot/Cargo.toml | 2 +- src/uu/cksum/Cargo.toml | 2 +- src/uu/comm/Cargo.toml | 2 +- src/uu/cp/Cargo.toml | 2 +- src/uu/csplit/Cargo.toml | 2 +- src/uu/cut/Cargo.toml | 2 +- src/uu/date/Cargo.toml | 2 +- src/uu/dd/Cargo.toml | 2 +- src/uu/df/Cargo.toml | 2 +- src/uu/dir/Cargo.toml | 2 +- src/uu/dircolors/Cargo.toml | 2 +- src/uu/dirname/Cargo.toml | 2 +- src/uu/du/Cargo.toml | 2 +- src/uu/echo/Cargo.toml | 2 +- src/uu/env/Cargo.toml | 2 +- src/uu/expand/Cargo.toml | 2 +- src/uu/expr/Cargo.toml | 2 +- src/uu/factor/Cargo.toml | 2 +- src/uu/false/Cargo.toml | 2 +- src/uu/fmt/Cargo.toml | 2 +- src/uu/fold/Cargo.toml | 2 +- src/uu/groups/Cargo.toml | 2 +- src/uu/hashsum/Cargo.toml | 2 +- src/uu/head/Cargo.toml | 2 +- src/uu/hostid/Cargo.toml | 2 +- src/uu/hostname/Cargo.toml | 2 +- src/uu/id/Cargo.toml | 2 +- src/uu/install/Cargo.toml | 2 +- src/uu/join/Cargo.toml | 2 +- src/uu/kill/Cargo.toml | 2 +- src/uu/link/Cargo.toml | 2 +- src/uu/ln/Cargo.toml | 2 +- src/uu/logname/Cargo.toml | 2 +- src/uu/ls/Cargo.toml | 2 +- src/uu/mkdir/Cargo.toml | 2 +- src/uu/mkfifo/Cargo.toml | 2 +- src/uu/mknod/Cargo.toml | 2 +- src/uu/mktemp/Cargo.toml | 2 +- src/uu/more/Cargo.toml | 2 +- src/uu/mv/Cargo.toml | 2 +- src/uu/nice/Cargo.toml | 2 +- src/uu/nl/Cargo.toml | 2 +- src/uu/nohup/Cargo.toml | 2 +- src/uu/nproc/Cargo.toml | 2 +- src/uu/numfmt/Cargo.toml | 2 +- src/uu/od/Cargo.toml | 2 +- src/uu/paste/Cargo.toml | 2 +- src/uu/pathchk/Cargo.toml | 2 +- src/uu/pinky/Cargo.toml | 2 +- src/uu/pr/Cargo.toml | 2 +- src/uu/printenv/Cargo.toml | 2 +- src/uu/printf/Cargo.toml | 2 +- src/uu/ptx/Cargo.toml | 2 +- src/uu/pwd/Cargo.toml | 2 +- src/uu/readlink/Cargo.toml | 2 +- src/uu/realpath/Cargo.toml | 2 +- src/uu/rm/Cargo.toml | 2 +- src/uu/rmdir/Cargo.toml | 2 +- src/uu/runcon/Cargo.toml | 2 +- src/uu/seq/Cargo.toml | 2 +- src/uu/shred/Cargo.toml | 2 +- src/uu/shuf/Cargo.toml | 2 +- src/uu/sleep/Cargo.toml | 2 +- src/uu/sort/Cargo.toml | 2 +- src/uu/split/Cargo.toml | 2 +- src/uu/stat/Cargo.toml | 2 +- src/uu/stdbuf/Cargo.toml | 4 +- src/uu/stdbuf/src/libstdbuf/Cargo.toml | 2 +- src/uu/stty/Cargo.toml | 2 +- src/uu/sum/Cargo.toml | 2 +- src/uu/sync/Cargo.toml | 2 +- src/uu/tac/Cargo.toml | 2 +- src/uu/tail/Cargo.toml | 2 +- src/uu/tee/Cargo.toml | 2 +- src/uu/test/Cargo.toml | 2 +- src/uu/timeout/Cargo.toml | 2 +- src/uu/touch/Cargo.toml | 2 +- src/uu/tr/Cargo.toml | 2 +- src/uu/true/Cargo.toml | 2 +- src/uu/truncate/Cargo.toml | 2 +- src/uu/tsort/Cargo.toml | 2 +- src/uu/tty/Cargo.toml | 2 +- src/uu/uname/Cargo.toml | 2 +- src/uu/unexpand/Cargo.toml | 2 +- src/uu/uniq/Cargo.toml | 2 +- src/uu/unlink/Cargo.toml | 2 +- src/uu/uptime/Cargo.toml | 2 +- src/uu/users/Cargo.toml | 2 +- src/uu/vdir/Cargo.toml | 2 +- src/uu/wc/Cargo.toml | 2 +- src/uu/who/Cargo.toml | 2 +- src/uu/whoami/Cargo.toml | 2 +- src/uu/yes/Cargo.toml | 2 +- src/uucore/Cargo.toml | 2 +- src/uucore_procs/Cargo.toml | 4 +- src/uuhelp_parser/Cargo.toml | 2 +- util/update-version.sh | 4 +- 109 files changed, 320 insertions(+), 320 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f9be5346f61..a54656d7c4a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -376,7 +376,7 @@ checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" [[package]] name = "coreutils" -version = "0.0.21" +version = "0.0.22" dependencies = [ "chrono", "clap", @@ -2225,7 +2225,7 @@ checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" [[package]] name = "uu_arch" -version = "0.0.21" +version = "0.0.22" dependencies = [ "clap", "platform-info", @@ -2234,7 +2234,7 @@ dependencies = [ [[package]] name = "uu_base32" -version = "0.0.21" +version = "0.0.22" dependencies = [ "clap", "uucore", @@ -2242,7 +2242,7 @@ dependencies = [ [[package]] name = "uu_base64" -version = "0.0.21" +version = "0.0.22" dependencies = [ "uu_base32", "uucore", @@ -2250,7 +2250,7 @@ dependencies = [ [[package]] name = "uu_basename" -version = "0.0.21" +version = "0.0.22" dependencies = [ "clap", "uucore", @@ -2258,7 +2258,7 @@ dependencies = [ [[package]] name = "uu_basenc" -version = "0.0.21" +version = "0.0.22" dependencies = [ "clap", "uu_base32", @@ -2267,7 +2267,7 @@ dependencies = [ [[package]] name = "uu_cat" -version = "0.0.21" +version = "0.0.22" dependencies = [ "clap", "nix", @@ -2277,7 +2277,7 @@ dependencies = [ [[package]] name = "uu_chcon" -version = "0.0.21" +version = "0.0.22" dependencies = [ "clap", "fts-sys", @@ -2289,7 +2289,7 @@ dependencies = [ [[package]] name = "uu_chgrp" -version = "0.0.21" +version = "0.0.22" dependencies = [ "clap", "uucore", @@ -2297,7 +2297,7 @@ dependencies = [ [[package]] name = "uu_chmod" -version = "0.0.21" +version = "0.0.22" dependencies = [ "clap", "libc", @@ -2306,7 +2306,7 @@ dependencies = [ [[package]] name = "uu_chown" -version = "0.0.21" +version = "0.0.22" dependencies = [ "clap", "uucore", @@ -2314,7 +2314,7 @@ dependencies = [ [[package]] name = "uu_chroot" -version = "0.0.21" +version = "0.0.22" dependencies = [ "clap", "uucore", @@ -2322,7 +2322,7 @@ dependencies = [ [[package]] name = "uu_cksum" -version = "0.0.21" +version = "0.0.22" dependencies = [ "clap", "hex", @@ -2331,7 +2331,7 @@ dependencies = [ [[package]] name = "uu_comm" -version = "0.0.21" +version = "0.0.22" dependencies = [ "clap", "uucore", @@ -2339,7 +2339,7 @@ dependencies = [ [[package]] name = "uu_cp" -version = "0.0.21" +version = "0.0.22" dependencies = [ "clap", "exacl", @@ -2355,7 +2355,7 @@ dependencies = [ [[package]] name = "uu_csplit" -version = "0.0.21" +version = "0.0.22" dependencies = [ "clap", "regex", @@ -2365,7 +2365,7 @@ dependencies = [ [[package]] name = "uu_cut" -version = "0.0.21" +version = "0.0.22" dependencies = [ "bstr", "clap", @@ -2375,7 +2375,7 @@ dependencies = [ [[package]] name = "uu_date" -version = "0.0.21" +version = "0.0.22" dependencies = [ "chrono", "clap", @@ -2387,7 +2387,7 @@ dependencies = [ [[package]] name = "uu_dd" -version = "0.0.21" +version = "0.0.22" dependencies = [ "clap", "gcd", @@ -2399,7 +2399,7 @@ dependencies = [ [[package]] name = "uu_df" -version = "0.0.21" +version = "0.0.22" dependencies = [ "clap", "tempfile", @@ -2409,7 +2409,7 @@ dependencies = [ [[package]] name = "uu_dir" -version = "0.0.21" +version = "0.0.22" dependencies = [ "clap", "uu_ls", @@ -2418,7 +2418,7 @@ dependencies = [ [[package]] name = "uu_dircolors" -version = "0.0.21" +version = "0.0.22" dependencies = [ "clap", "uucore", @@ -2426,7 +2426,7 @@ dependencies = [ [[package]] name = "uu_dirname" -version = "0.0.21" +version = "0.0.22" dependencies = [ "clap", "uucore", @@ -2434,7 +2434,7 @@ dependencies = [ [[package]] name = "uu_du" -version = "0.0.21" +version = "0.0.22" dependencies = [ "chrono", "clap", @@ -2445,7 +2445,7 @@ dependencies = [ [[package]] name = "uu_echo" -version = "0.0.21" +version = "0.0.22" dependencies = [ "clap", "uucore", @@ -2453,7 +2453,7 @@ dependencies = [ [[package]] name = "uu_env" -version = "0.0.21" +version = "0.0.22" dependencies = [ "clap", "nix", @@ -2463,7 +2463,7 @@ dependencies = [ [[package]] name = "uu_expand" -version = "0.0.21" +version = "0.0.22" dependencies = [ "clap", "unicode-width", @@ -2472,7 +2472,7 @@ dependencies = [ [[package]] name = "uu_expr" -version = "0.0.21" +version = "0.0.22" dependencies = [ "clap", "num-bigint", @@ -2483,7 +2483,7 @@ dependencies = [ [[package]] name = "uu_factor" -version = "0.0.21" +version = "0.0.22" dependencies = [ "clap", "coz", @@ -2496,7 +2496,7 @@ dependencies = [ [[package]] name = "uu_false" -version = "0.0.21" +version = "0.0.22" dependencies = [ "clap", "uucore", @@ -2504,7 +2504,7 @@ dependencies = [ [[package]] name = "uu_fmt" -version = "0.0.21" +version = "0.0.22" dependencies = [ "clap", "unicode-width", @@ -2513,7 +2513,7 @@ dependencies = [ [[package]] name = "uu_fold" -version = "0.0.21" +version = "0.0.22" dependencies = [ "clap", "uucore", @@ -2521,7 +2521,7 @@ dependencies = [ [[package]] name = "uu_groups" -version = "0.0.21" +version = "0.0.22" dependencies = [ "clap", "uucore", @@ -2529,7 +2529,7 @@ dependencies = [ [[package]] name = "uu_hashsum" -version = "0.0.21" +version = "0.0.22" dependencies = [ "clap", "hex", @@ -2540,7 +2540,7 @@ dependencies = [ [[package]] name = "uu_head" -version = "0.0.21" +version = "0.0.22" dependencies = [ "clap", "memchr", @@ -2549,7 +2549,7 @@ dependencies = [ [[package]] name = "uu_hostid" -version = "0.0.21" +version = "0.0.22" dependencies = [ "clap", "libc", @@ -2558,7 +2558,7 @@ dependencies = [ [[package]] name = "uu_hostname" -version = "0.0.21" +version = "0.0.22" dependencies = [ "clap", "hostname", @@ -2568,7 +2568,7 @@ dependencies = [ [[package]] name = "uu_id" -version = "0.0.21" +version = "0.0.22" dependencies = [ "clap", "selinux", @@ -2577,7 +2577,7 @@ dependencies = [ [[package]] name = "uu_install" -version = "0.0.21" +version = "0.0.22" dependencies = [ "clap", "file_diff", @@ -2588,7 +2588,7 @@ dependencies = [ [[package]] name = "uu_join" -version = "0.0.21" +version = "0.0.22" dependencies = [ "clap", "memchr", @@ -2597,7 +2597,7 @@ dependencies = [ [[package]] name = "uu_kill" -version = "0.0.21" +version = "0.0.22" dependencies = [ "clap", "nix", @@ -2606,7 +2606,7 @@ dependencies = [ [[package]] name = "uu_link" -version = "0.0.21" +version = "0.0.22" dependencies = [ "clap", "uucore", @@ -2614,7 +2614,7 @@ dependencies = [ [[package]] name = "uu_ln" -version = "0.0.21" +version = "0.0.22" dependencies = [ "clap", "uucore", @@ -2622,7 +2622,7 @@ dependencies = [ [[package]] name = "uu_logname" -version = "0.0.21" +version = "0.0.22" dependencies = [ "clap", "libc", @@ -2631,7 +2631,7 @@ dependencies = [ [[package]] name = "uu_ls" -version = "0.0.21" +version = "0.0.22" dependencies = [ "chrono", "clap", @@ -2648,7 +2648,7 @@ dependencies = [ [[package]] name = "uu_mkdir" -version = "0.0.21" +version = "0.0.22" dependencies = [ "clap", "uucore", @@ -2656,7 +2656,7 @@ dependencies = [ [[package]] name = "uu_mkfifo" -version = "0.0.21" +version = "0.0.22" dependencies = [ "clap", "libc", @@ -2665,7 +2665,7 @@ dependencies = [ [[package]] name = "uu_mknod" -version = "0.0.21" +version = "0.0.22" dependencies = [ "clap", "libc", @@ -2674,7 +2674,7 @@ dependencies = [ [[package]] name = "uu_mktemp" -version = "0.0.21" +version = "0.0.22" dependencies = [ "clap", "rand", @@ -2684,7 +2684,7 @@ dependencies = [ [[package]] name = "uu_more" -version = "0.0.21" +version = "0.0.22" dependencies = [ "clap", "crossterm", @@ -2696,7 +2696,7 @@ dependencies = [ [[package]] name = "uu_mv" -version = "0.0.21" +version = "0.0.22" dependencies = [ "clap", "fs_extra", @@ -2706,7 +2706,7 @@ dependencies = [ [[package]] name = "uu_nice" -version = "0.0.21" +version = "0.0.22" dependencies = [ "clap", "libc", @@ -2716,7 +2716,7 @@ dependencies = [ [[package]] name = "uu_nl" -version = "0.0.21" +version = "0.0.22" dependencies = [ "clap", "regex", @@ -2725,7 +2725,7 @@ dependencies = [ [[package]] name = "uu_nohup" -version = "0.0.21" +version = "0.0.22" dependencies = [ "clap", "libc", @@ -2734,7 +2734,7 @@ dependencies = [ [[package]] name = "uu_nproc" -version = "0.0.21" +version = "0.0.22" dependencies = [ "clap", "libc", @@ -2743,7 +2743,7 @@ dependencies = [ [[package]] name = "uu_numfmt" -version = "0.0.21" +version = "0.0.22" dependencies = [ "clap", "uucore", @@ -2751,7 +2751,7 @@ dependencies = [ [[package]] name = "uu_od" -version = "0.0.21" +version = "0.0.22" dependencies = [ "byteorder", "clap", @@ -2761,7 +2761,7 @@ dependencies = [ [[package]] name = "uu_paste" -version = "0.0.21" +version = "0.0.22" dependencies = [ "clap", "uucore", @@ -2769,7 +2769,7 @@ dependencies = [ [[package]] name = "uu_pathchk" -version = "0.0.21" +version = "0.0.22" dependencies = [ "clap", "libc", @@ -2778,7 +2778,7 @@ dependencies = [ [[package]] name = "uu_pinky" -version = "0.0.21" +version = "0.0.22" dependencies = [ "clap", "uucore", @@ -2786,7 +2786,7 @@ dependencies = [ [[package]] name = "uu_pr" -version = "0.0.21" +version = "0.0.22" dependencies = [ "chrono", "clap", @@ -2798,7 +2798,7 @@ dependencies = [ [[package]] name = "uu_printenv" -version = "0.0.21" +version = "0.0.22" dependencies = [ "clap", "uucore", @@ -2806,7 +2806,7 @@ dependencies = [ [[package]] name = "uu_printf" -version = "0.0.21" +version = "0.0.22" dependencies = [ "clap", "uucore", @@ -2814,7 +2814,7 @@ dependencies = [ [[package]] name = "uu_ptx" -version = "0.0.21" +version = "0.0.22" dependencies = [ "clap", "regex", @@ -2823,7 +2823,7 @@ dependencies = [ [[package]] name = "uu_pwd" -version = "0.0.21" +version = "0.0.22" dependencies = [ "clap", "uucore", @@ -2831,7 +2831,7 @@ dependencies = [ [[package]] name = "uu_readlink" -version = "0.0.21" +version = "0.0.22" dependencies = [ "clap", "uucore", @@ -2839,7 +2839,7 @@ dependencies = [ [[package]] name = "uu_realpath" -version = "0.0.21" +version = "0.0.22" dependencies = [ "clap", "uucore", @@ -2847,7 +2847,7 @@ dependencies = [ [[package]] name = "uu_rm" -version = "0.0.21" +version = "0.0.22" dependencies = [ "clap", "libc", @@ -2858,7 +2858,7 @@ dependencies = [ [[package]] name = "uu_rmdir" -version = "0.0.21" +version = "0.0.22" dependencies = [ "clap", "libc", @@ -2867,7 +2867,7 @@ dependencies = [ [[package]] name = "uu_runcon" -version = "0.0.21" +version = "0.0.22" dependencies = [ "clap", "libc", @@ -2878,7 +2878,7 @@ dependencies = [ [[package]] name = "uu_seq" -version = "0.0.21" +version = "0.0.22" dependencies = [ "bigdecimal", "clap", @@ -2889,7 +2889,7 @@ dependencies = [ [[package]] name = "uu_shred" -version = "0.0.21" +version = "0.0.22" dependencies = [ "clap", "libc", @@ -2899,7 +2899,7 @@ dependencies = [ [[package]] name = "uu_shuf" -version = "0.0.21" +version = "0.0.22" dependencies = [ "clap", "memchr", @@ -2910,7 +2910,7 @@ dependencies = [ [[package]] name = "uu_sleep" -version = "0.0.21" +version = "0.0.22" dependencies = [ "clap", "fundu", @@ -2919,7 +2919,7 @@ dependencies = [ [[package]] name = "uu_sort" -version = "0.0.21" +version = "0.0.22" dependencies = [ "binary-heap-plus", "clap", @@ -2938,7 +2938,7 @@ dependencies = [ [[package]] name = "uu_split" -version = "0.0.21" +version = "0.0.22" dependencies = [ "clap", "memchr", @@ -2947,7 +2947,7 @@ dependencies = [ [[package]] name = "uu_stat" -version = "0.0.21" +version = "0.0.22" dependencies = [ "clap", "uucore", @@ -2955,7 +2955,7 @@ dependencies = [ [[package]] name = "uu_stdbuf" -version = "0.0.21" +version = "0.0.22" dependencies = [ "clap", "tempfile", @@ -2965,7 +2965,7 @@ dependencies = [ [[package]] name = "uu_stdbuf_libstdbuf" -version = "0.0.21" +version = "0.0.22" dependencies = [ "cpp", "cpp_build", @@ -2975,7 +2975,7 @@ dependencies = [ [[package]] name = "uu_stty" -version = "0.0.21" +version = "0.0.22" dependencies = [ "clap", "nix", @@ -2984,7 +2984,7 @@ dependencies = [ [[package]] name = "uu_sum" -version = "0.0.21" +version = "0.0.22" dependencies = [ "clap", "uucore", @@ -2992,7 +2992,7 @@ dependencies = [ [[package]] name = "uu_sync" -version = "0.0.21" +version = "0.0.22" dependencies = [ "clap", "libc", @@ -3003,7 +3003,7 @@ dependencies = [ [[package]] name = "uu_tac" -version = "0.0.21" +version = "0.0.22" dependencies = [ "clap", "memchr", @@ -3014,7 +3014,7 @@ dependencies = [ [[package]] name = "uu_tail" -version = "0.0.21" +version = "0.0.22" dependencies = [ "clap", "fundu", @@ -3030,7 +3030,7 @@ dependencies = [ [[package]] name = "uu_tee" -version = "0.0.21" +version = "0.0.22" dependencies = [ "clap", "libc", @@ -3039,7 +3039,7 @@ dependencies = [ [[package]] name = "uu_test" -version = "0.0.21" +version = "0.0.22" dependencies = [ "clap", "libc", @@ -3049,7 +3049,7 @@ dependencies = [ [[package]] name = "uu_timeout" -version = "0.0.21" +version = "0.0.22" dependencies = [ "clap", "libc", @@ -3059,7 +3059,7 @@ dependencies = [ [[package]] name = "uu_touch" -version = "0.0.21" +version = "0.0.22" dependencies = [ "chrono", "clap", @@ -3071,7 +3071,7 @@ dependencies = [ [[package]] name = "uu_tr" -version = "0.0.21" +version = "0.0.22" dependencies = [ "clap", "nom", @@ -3080,7 +3080,7 @@ dependencies = [ [[package]] name = "uu_true" -version = "0.0.21" +version = "0.0.22" dependencies = [ "clap", "uucore", @@ -3088,7 +3088,7 @@ dependencies = [ [[package]] name = "uu_truncate" -version = "0.0.21" +version = "0.0.22" dependencies = [ "clap", "uucore", @@ -3096,7 +3096,7 @@ dependencies = [ [[package]] name = "uu_tsort" -version = "0.0.21" +version = "0.0.22" dependencies = [ "clap", "uucore", @@ -3104,7 +3104,7 @@ dependencies = [ [[package]] name = "uu_tty" -version = "0.0.21" +version = "0.0.22" dependencies = [ "clap", "nix", @@ -3113,7 +3113,7 @@ dependencies = [ [[package]] name = "uu_uname" -version = "0.0.21" +version = "0.0.22" dependencies = [ "clap", "platform-info", @@ -3122,7 +3122,7 @@ dependencies = [ [[package]] name = "uu_unexpand" -version = "0.0.21" +version = "0.0.22" dependencies = [ "clap", "unicode-width", @@ -3131,7 +3131,7 @@ dependencies = [ [[package]] name = "uu_uniq" -version = "0.0.21" +version = "0.0.22" dependencies = [ "clap", "uucore", @@ -3139,7 +3139,7 @@ dependencies = [ [[package]] name = "uu_unlink" -version = "0.0.21" +version = "0.0.22" dependencies = [ "clap", "uucore", @@ -3147,7 +3147,7 @@ dependencies = [ [[package]] name = "uu_uptime" -version = "0.0.21" +version = "0.0.22" dependencies = [ "chrono", "clap", @@ -3156,7 +3156,7 @@ dependencies = [ [[package]] name = "uu_users" -version = "0.0.21" +version = "0.0.22" dependencies = [ "clap", "uucore", @@ -3164,7 +3164,7 @@ dependencies = [ [[package]] name = "uu_vdir" -version = "0.0.21" +version = "0.0.22" dependencies = [ "clap", "uu_ls", @@ -3173,7 +3173,7 @@ dependencies = [ [[package]] name = "uu_wc" -version = "0.0.21" +version = "0.0.22" dependencies = [ "bytecount", "clap", @@ -3186,7 +3186,7 @@ dependencies = [ [[package]] name = "uu_who" -version = "0.0.21" +version = "0.0.22" dependencies = [ "clap", "uucore", @@ -3194,7 +3194,7 @@ dependencies = [ [[package]] name = "uu_whoami" -version = "0.0.21" +version = "0.0.22" dependencies = [ "clap", "libc", @@ -3204,7 +3204,7 @@ dependencies = [ [[package]] name = "uu_yes" -version = "0.0.21" +version = "0.0.22" dependencies = [ "clap", "itertools", @@ -3214,7 +3214,7 @@ dependencies = [ [[package]] name = "uucore" -version = "0.0.21" +version = "0.0.22" dependencies = [ "blake2b_simd", "blake3", @@ -3250,7 +3250,7 @@ dependencies = [ [[package]] name = "uucore_procs" -version = "0.0.21" +version = "0.0.22" dependencies = [ "proc-macro2", "quote", @@ -3259,7 +3259,7 @@ dependencies = [ [[package]] name = "uuhelp_parser" -version = "0.0.21" +version = "0.0.22" [[package]] name = "uuid" diff --git a/Cargo.toml b/Cargo.toml index 975cced35bb..39232b50d64 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ [package] name = "coreutils" -version = "0.0.21" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "coreutils ~ GNU coreutils (updated); implemented as universal (cross-platform) utils, written in Rust" @@ -361,109 +361,109 @@ zip = { workspace = true, optional = true } uuhelp_parser = { optional = true, version = ">=0.0.19", path = "src/uuhelp_parser" } # * uutils -uu_test = { optional = true, version = "0.0.21", package = "uu_test", path = "src/uu/test" } +uu_test = { optional = true, version = "0.0.22", package = "uu_test", path = "src/uu/test" } # -arch = { optional = true, version = "0.0.21", package = "uu_arch", path = "src/uu/arch" } -base32 = { optional = true, version = "0.0.21", package = "uu_base32", path = "src/uu/base32" } -base64 = { optional = true, version = "0.0.21", package = "uu_base64", path = "src/uu/base64" } -basename = { optional = true, version = "0.0.21", package = "uu_basename", path = "src/uu/basename" } -basenc = { optional = true, version = "0.0.21", package = "uu_basenc", path = "src/uu/basenc" } -cat = { optional = true, version = "0.0.21", package = "uu_cat", path = "src/uu/cat" } -chcon = { optional = true, version = "0.0.21", package = "uu_chcon", path = "src/uu/chcon" } -chgrp = { optional = true, version = "0.0.21", package = "uu_chgrp", path = "src/uu/chgrp" } -chmod = { optional = true, version = "0.0.21", package = "uu_chmod", path = "src/uu/chmod" } -chown = { optional = true, version = "0.0.21", package = "uu_chown", path = "src/uu/chown" } -chroot = { optional = true, version = "0.0.21", package = "uu_chroot", path = "src/uu/chroot" } -cksum = { optional = true, version = "0.0.21", package = "uu_cksum", path = "src/uu/cksum" } -comm = { optional = true, version = "0.0.21", package = "uu_comm", path = "src/uu/comm" } -cp = { optional = true, version = "0.0.21", package = "uu_cp", path = "src/uu/cp" } -csplit = { optional = true, version = "0.0.21", package = "uu_csplit", path = "src/uu/csplit" } -cut = { optional = true, version = "0.0.21", package = "uu_cut", path = "src/uu/cut" } -date = { optional = true, version = "0.0.21", package = "uu_date", path = "src/uu/date" } -dd = { optional = true, version = "0.0.21", package = "uu_dd", path = "src/uu/dd" } -df = { optional = true, version = "0.0.21", package = "uu_df", path = "src/uu/df" } -dir = { optional = true, version = "0.0.21", package = "uu_dir", path = "src/uu/dir" } -dircolors = { optional = true, version = "0.0.21", package = "uu_dircolors", path = "src/uu/dircolors" } -dirname = { optional = true, version = "0.0.21", package = "uu_dirname", path = "src/uu/dirname" } -du = { optional = true, version = "0.0.21", package = "uu_du", path = "src/uu/du" } -echo = { optional = true, version = "0.0.21", package = "uu_echo", path = "src/uu/echo" } -env = { optional = true, version = "0.0.21", package = "uu_env", path = "src/uu/env" } -expand = { optional = true, version = "0.0.21", package = "uu_expand", path = "src/uu/expand" } -expr = { optional = true, version = "0.0.21", package = "uu_expr", path = "src/uu/expr" } -factor = { optional = true, version = "0.0.21", package = "uu_factor", path = "src/uu/factor" } -false = { optional = true, version = "0.0.21", package = "uu_false", path = "src/uu/false" } -fmt = { optional = true, version = "0.0.21", package = "uu_fmt", path = "src/uu/fmt" } -fold = { optional = true, version = "0.0.21", package = "uu_fold", path = "src/uu/fold" } -groups = { optional = true, version = "0.0.21", package = "uu_groups", path = "src/uu/groups" } -hashsum = { optional = true, version = "0.0.21", package = "uu_hashsum", path = "src/uu/hashsum" } -head = { optional = true, version = "0.0.21", package = "uu_head", path = "src/uu/head" } -hostid = { optional = true, version = "0.0.21", package = "uu_hostid", path = "src/uu/hostid" } -hostname = { optional = true, version = "0.0.21", package = "uu_hostname", path = "src/uu/hostname" } -id = { optional = true, version = "0.0.21", package = "uu_id", path = "src/uu/id" } -install = { optional = true, version = "0.0.21", package = "uu_install", path = "src/uu/install" } -join = { optional = true, version = "0.0.21", package = "uu_join", path = "src/uu/join" } -kill = { optional = true, version = "0.0.21", package = "uu_kill", path = "src/uu/kill" } -link = { optional = true, version = "0.0.21", package = "uu_link", path = "src/uu/link" } -ln = { optional = true, version = "0.0.21", package = "uu_ln", path = "src/uu/ln" } -ls = { optional = true, version = "0.0.21", package = "uu_ls", path = "src/uu/ls" } -logname = { optional = true, version = "0.0.21", package = "uu_logname", path = "src/uu/logname" } -mkdir = { optional = true, version = "0.0.21", package = "uu_mkdir", path = "src/uu/mkdir" } -mkfifo = { optional = true, version = "0.0.21", package = "uu_mkfifo", path = "src/uu/mkfifo" } -mknod = { optional = true, version = "0.0.21", package = "uu_mknod", path = "src/uu/mknod" } -mktemp = { optional = true, version = "0.0.21", package = "uu_mktemp", path = "src/uu/mktemp" } -more = { optional = true, version = "0.0.21", package = "uu_more", path = "src/uu/more" } -mv = { optional = true, version = "0.0.21", package = "uu_mv", path = "src/uu/mv" } -nice = { optional = true, version = "0.0.21", package = "uu_nice", path = "src/uu/nice" } -nl = { optional = true, version = "0.0.21", package = "uu_nl", path = "src/uu/nl" } -nohup = { optional = true, version = "0.0.21", package = "uu_nohup", path = "src/uu/nohup" } -nproc = { optional = true, version = "0.0.21", package = "uu_nproc", path = "src/uu/nproc" } -numfmt = { optional = true, version = "0.0.21", package = "uu_numfmt", path = "src/uu/numfmt" } -od = { optional = true, version = "0.0.21", package = "uu_od", path = "src/uu/od" } -paste = { optional = true, version = "0.0.21", package = "uu_paste", path = "src/uu/paste" } -pathchk = { optional = true, version = "0.0.21", package = "uu_pathchk", path = "src/uu/pathchk" } -pinky = { optional = true, version = "0.0.21", package = "uu_pinky", path = "src/uu/pinky" } -pr = { optional = true, version = "0.0.21", package = "uu_pr", path = "src/uu/pr" } -printenv = { optional = true, version = "0.0.21", package = "uu_printenv", path = "src/uu/printenv" } -printf = { optional = true, version = "0.0.21", package = "uu_printf", path = "src/uu/printf" } -ptx = { optional = true, version = "0.0.21", package = "uu_ptx", path = "src/uu/ptx" } -pwd = { optional = true, version = "0.0.21", package = "uu_pwd", path = "src/uu/pwd" } -readlink = { optional = true, version = "0.0.21", package = "uu_readlink", path = "src/uu/readlink" } -realpath = { optional = true, version = "0.0.21", package = "uu_realpath", path = "src/uu/realpath" } -rm = { optional = true, version = "0.0.21", package = "uu_rm", path = "src/uu/rm" } -rmdir = { optional = true, version = "0.0.21", package = "uu_rmdir", path = "src/uu/rmdir" } -runcon = { optional = true, version = "0.0.21", package = "uu_runcon", path = "src/uu/runcon" } -seq = { optional = true, version = "0.0.21", package = "uu_seq", path = "src/uu/seq" } -shred = { optional = true, version = "0.0.21", package = "uu_shred", path = "src/uu/shred" } -shuf = { optional = true, version = "0.0.21", package = "uu_shuf", path = "src/uu/shuf" } -sleep = { optional = true, version = "0.0.21", package = "uu_sleep", path = "src/uu/sleep" } -sort = { optional = true, version = "0.0.21", package = "uu_sort", path = "src/uu/sort" } -split = { optional = true, version = "0.0.21", package = "uu_split", path = "src/uu/split" } -stat = { optional = true, version = "0.0.21", package = "uu_stat", path = "src/uu/stat" } -stdbuf = { optional = true, version = "0.0.21", package = "uu_stdbuf", path = "src/uu/stdbuf" } -stty = { optional = true, version = "0.0.21", package = "uu_stty", path = "src/uu/stty" } -sum = { optional = true, version = "0.0.21", package = "uu_sum", path = "src/uu/sum" } -sync = { optional = true, version = "0.0.21", package = "uu_sync", path = "src/uu/sync" } -tac = { optional = true, version = "0.0.21", package = "uu_tac", path = "src/uu/tac" } -tail = { optional = true, version = "0.0.21", package = "uu_tail", path = "src/uu/tail" } -tee = { optional = true, version = "0.0.21", package = "uu_tee", path = "src/uu/tee" } -timeout = { optional = true, version = "0.0.21", package = "uu_timeout", path = "src/uu/timeout" } -touch = { optional = true, version = "0.0.21", package = "uu_touch", path = "src/uu/touch" } -tr = { optional = true, version = "0.0.21", package = "uu_tr", path = "src/uu/tr" } -true = { optional = true, version = "0.0.21", package = "uu_true", path = "src/uu/true" } -truncate = { optional = true, version = "0.0.21", package = "uu_truncate", path = "src/uu/truncate" } -tsort = { optional = true, version = "0.0.21", package = "uu_tsort", path = "src/uu/tsort" } -tty = { optional = true, version = "0.0.21", package = "uu_tty", path = "src/uu/tty" } -uname = { optional = true, version = "0.0.21", package = "uu_uname", path = "src/uu/uname" } -unexpand = { optional = true, version = "0.0.21", package = "uu_unexpand", path = "src/uu/unexpand" } -uniq = { optional = true, version = "0.0.21", package = "uu_uniq", path = "src/uu/uniq" } -unlink = { optional = true, version = "0.0.21", package = "uu_unlink", path = "src/uu/unlink" } -uptime = { optional = true, version = "0.0.21", package = "uu_uptime", path = "src/uu/uptime" } -users = { optional = true, version = "0.0.21", package = "uu_users", path = "src/uu/users" } -vdir = { optional = true, version = "0.0.21", package = "uu_vdir", path = "src/uu/vdir" } -wc = { optional = true, version = "0.0.21", package = "uu_wc", path = "src/uu/wc" } -who = { optional = true, version = "0.0.21", package = "uu_who", path = "src/uu/who" } -whoami = { optional = true, version = "0.0.21", package = "uu_whoami", path = "src/uu/whoami" } -yes = { optional = true, version = "0.0.21", package = "uu_yes", path = "src/uu/yes" } +arch = { optional = true, version = "0.0.22", package = "uu_arch", path = "src/uu/arch" } +base32 = { optional = true, version = "0.0.22", package = "uu_base32", path = "src/uu/base32" } +base64 = { optional = true, version = "0.0.22", package = "uu_base64", path = "src/uu/base64" } +basename = { optional = true, version = "0.0.22", package = "uu_basename", path = "src/uu/basename" } +basenc = { optional = true, version = "0.0.22", package = "uu_basenc", path = "src/uu/basenc" } +cat = { optional = true, version = "0.0.22", package = "uu_cat", path = "src/uu/cat" } +chcon = { optional = true, version = "0.0.22", package = "uu_chcon", path = "src/uu/chcon" } +chgrp = { optional = true, version = "0.0.22", package = "uu_chgrp", path = "src/uu/chgrp" } +chmod = { optional = true, version = "0.0.22", package = "uu_chmod", path = "src/uu/chmod" } +chown = { optional = true, version = "0.0.22", package = "uu_chown", path = "src/uu/chown" } +chroot = { optional = true, version = "0.0.22", package = "uu_chroot", path = "src/uu/chroot" } +cksum = { optional = true, version = "0.0.22", package = "uu_cksum", path = "src/uu/cksum" } +comm = { optional = true, version = "0.0.22", package = "uu_comm", path = "src/uu/comm" } +cp = { optional = true, version = "0.0.22", package = "uu_cp", path = "src/uu/cp" } +csplit = { optional = true, version = "0.0.22", package = "uu_csplit", path = "src/uu/csplit" } +cut = { optional = true, version = "0.0.22", package = "uu_cut", path = "src/uu/cut" } +date = { optional = true, version = "0.0.22", package = "uu_date", path = "src/uu/date" } +dd = { optional = true, version = "0.0.22", package = "uu_dd", path = "src/uu/dd" } +df = { optional = true, version = "0.0.22", package = "uu_df", path = "src/uu/df" } +dir = { optional = true, version = "0.0.22", package = "uu_dir", path = "src/uu/dir" } +dircolors = { optional = true, version = "0.0.22", package = "uu_dircolors", path = "src/uu/dircolors" } +dirname = { optional = true, version = "0.0.22", package = "uu_dirname", path = "src/uu/dirname" } +du = { optional = true, version = "0.0.22", package = "uu_du", path = "src/uu/du" } +echo = { optional = true, version = "0.0.22", package = "uu_echo", path = "src/uu/echo" } +env = { optional = true, version = "0.0.22", package = "uu_env", path = "src/uu/env" } +expand = { optional = true, version = "0.0.22", package = "uu_expand", path = "src/uu/expand" } +expr = { optional = true, version = "0.0.22", package = "uu_expr", path = "src/uu/expr" } +factor = { optional = true, version = "0.0.22", package = "uu_factor", path = "src/uu/factor" } +false = { optional = true, version = "0.0.22", package = "uu_false", path = "src/uu/false" } +fmt = { optional = true, version = "0.0.22", package = "uu_fmt", path = "src/uu/fmt" } +fold = { optional = true, version = "0.0.22", package = "uu_fold", path = "src/uu/fold" } +groups = { optional = true, version = "0.0.22", package = "uu_groups", path = "src/uu/groups" } +hashsum = { optional = true, version = "0.0.22", package = "uu_hashsum", path = "src/uu/hashsum" } +head = { optional = true, version = "0.0.22", package = "uu_head", path = "src/uu/head" } +hostid = { optional = true, version = "0.0.22", package = "uu_hostid", path = "src/uu/hostid" } +hostname = { optional = true, version = "0.0.22", package = "uu_hostname", path = "src/uu/hostname" } +id = { optional = true, version = "0.0.22", package = "uu_id", path = "src/uu/id" } +install = { optional = true, version = "0.0.22", package = "uu_install", path = "src/uu/install" } +join = { optional = true, version = "0.0.22", package = "uu_join", path = "src/uu/join" } +kill = { optional = true, version = "0.0.22", package = "uu_kill", path = "src/uu/kill" } +link = { optional = true, version = "0.0.22", package = "uu_link", path = "src/uu/link" } +ln = { optional = true, version = "0.0.22", package = "uu_ln", path = "src/uu/ln" } +ls = { optional = true, version = "0.0.22", package = "uu_ls", path = "src/uu/ls" } +logname = { optional = true, version = "0.0.22", package = "uu_logname", path = "src/uu/logname" } +mkdir = { optional = true, version = "0.0.22", package = "uu_mkdir", path = "src/uu/mkdir" } +mkfifo = { optional = true, version = "0.0.22", package = "uu_mkfifo", path = "src/uu/mkfifo" } +mknod = { optional = true, version = "0.0.22", package = "uu_mknod", path = "src/uu/mknod" } +mktemp = { optional = true, version = "0.0.22", package = "uu_mktemp", path = "src/uu/mktemp" } +more = { optional = true, version = "0.0.22", package = "uu_more", path = "src/uu/more" } +mv = { optional = true, version = "0.0.22", package = "uu_mv", path = "src/uu/mv" } +nice = { optional = true, version = "0.0.22", package = "uu_nice", path = "src/uu/nice" } +nl = { optional = true, version = "0.0.22", package = "uu_nl", path = "src/uu/nl" } +nohup = { optional = true, version = "0.0.22", package = "uu_nohup", path = "src/uu/nohup" } +nproc = { optional = true, version = "0.0.22", package = "uu_nproc", path = "src/uu/nproc" } +numfmt = { optional = true, version = "0.0.22", package = "uu_numfmt", path = "src/uu/numfmt" } +od = { optional = true, version = "0.0.22", package = "uu_od", path = "src/uu/od" } +paste = { optional = true, version = "0.0.22", package = "uu_paste", path = "src/uu/paste" } +pathchk = { optional = true, version = "0.0.22", package = "uu_pathchk", path = "src/uu/pathchk" } +pinky = { optional = true, version = "0.0.22", package = "uu_pinky", path = "src/uu/pinky" } +pr = { optional = true, version = "0.0.22", package = "uu_pr", path = "src/uu/pr" } +printenv = { optional = true, version = "0.0.22", package = "uu_printenv", path = "src/uu/printenv" } +printf = { optional = true, version = "0.0.22", package = "uu_printf", path = "src/uu/printf" } +ptx = { optional = true, version = "0.0.22", package = "uu_ptx", path = "src/uu/ptx" } +pwd = { optional = true, version = "0.0.22", package = "uu_pwd", path = "src/uu/pwd" } +readlink = { optional = true, version = "0.0.22", package = "uu_readlink", path = "src/uu/readlink" } +realpath = { optional = true, version = "0.0.22", package = "uu_realpath", path = "src/uu/realpath" } +rm = { optional = true, version = "0.0.22", package = "uu_rm", path = "src/uu/rm" } +rmdir = { optional = true, version = "0.0.22", package = "uu_rmdir", path = "src/uu/rmdir" } +runcon = { optional = true, version = "0.0.22", package = "uu_runcon", path = "src/uu/runcon" } +seq = { optional = true, version = "0.0.22", package = "uu_seq", path = "src/uu/seq" } +shred = { optional = true, version = "0.0.22", package = "uu_shred", path = "src/uu/shred" } +shuf = { optional = true, version = "0.0.22", package = "uu_shuf", path = "src/uu/shuf" } +sleep = { optional = true, version = "0.0.22", package = "uu_sleep", path = "src/uu/sleep" } +sort = { optional = true, version = "0.0.22", package = "uu_sort", path = "src/uu/sort" } +split = { optional = true, version = "0.0.22", package = "uu_split", path = "src/uu/split" } +stat = { optional = true, version = "0.0.22", package = "uu_stat", path = "src/uu/stat" } +stdbuf = { optional = true, version = "0.0.22", package = "uu_stdbuf", path = "src/uu/stdbuf" } +stty = { optional = true, version = "0.0.22", package = "uu_stty", path = "src/uu/stty" } +sum = { optional = true, version = "0.0.22", package = "uu_sum", path = "src/uu/sum" } +sync = { optional = true, version = "0.0.22", package = "uu_sync", path = "src/uu/sync" } +tac = { optional = true, version = "0.0.22", package = "uu_tac", path = "src/uu/tac" } +tail = { optional = true, version = "0.0.22", package = "uu_tail", path = "src/uu/tail" } +tee = { optional = true, version = "0.0.22", package = "uu_tee", path = "src/uu/tee" } +timeout = { optional = true, version = "0.0.22", package = "uu_timeout", path = "src/uu/timeout" } +touch = { optional = true, version = "0.0.22", package = "uu_touch", path = "src/uu/touch" } +tr = { optional = true, version = "0.0.22", package = "uu_tr", path = "src/uu/tr" } +true = { optional = true, version = "0.0.22", package = "uu_true", path = "src/uu/true" } +truncate = { optional = true, version = "0.0.22", package = "uu_truncate", path = "src/uu/truncate" } +tsort = { optional = true, version = "0.0.22", package = "uu_tsort", path = "src/uu/tsort" } +tty = { optional = true, version = "0.0.22", package = "uu_tty", path = "src/uu/tty" } +uname = { optional = true, version = "0.0.22", package = "uu_uname", path = "src/uu/uname" } +unexpand = { optional = true, version = "0.0.22", package = "uu_unexpand", path = "src/uu/unexpand" } +uniq = { optional = true, version = "0.0.22", package = "uu_uniq", path = "src/uu/uniq" } +unlink = { optional = true, version = "0.0.22", package = "uu_unlink", path = "src/uu/unlink" } +uptime = { optional = true, version = "0.0.22", package = "uu_uptime", path = "src/uu/uptime" } +users = { optional = true, version = "0.0.22", package = "uu_users", path = "src/uu/users" } +vdir = { optional = true, version = "0.0.22", package = "uu_vdir", path = "src/uu/vdir" } +wc = { optional = true, version = "0.0.22", package = "uu_wc", path = "src/uu/wc" } +who = { optional = true, version = "0.0.22", package = "uu_who", path = "src/uu/who" } +whoami = { optional = true, version = "0.0.22", package = "uu_whoami", path = "src/uu/whoami" } +yes = { optional = true, version = "0.0.22", package = "uu_yes", path = "src/uu/yes" } # this breaks clippy linting with: "tests/by-util/test_factor_benches.rs: No such file or directory (os error 2)" # factor_benches = { optional = true, version = "0.0.0", package = "uu_factor_benches", path = "tests/benches/factor" } diff --git a/src/uu/arch/Cargo.toml b/src/uu/arch/Cargo.toml index cd12cb4d483..9686c3f0acd 100644 --- a/src/uu/arch/Cargo.toml +++ b/src/uu/arch/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_arch" -version = "0.0.21" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "arch ~ (uutils) display machine architecture" diff --git a/src/uu/base32/Cargo.toml b/src/uu/base32/Cargo.toml index 044b92d23fc..f3bca4a1972 100644 --- a/src/uu/base32/Cargo.toml +++ b/src/uu/base32/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_base32" -version = "0.0.21" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "base32 ~ (uutils) decode/encode input (base32-encoding)" diff --git a/src/uu/base64/Cargo.toml b/src/uu/base64/Cargo.toml index 3bc04515280..ddd669b89b6 100644 --- a/src/uu/base64/Cargo.toml +++ b/src/uu/base64/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_base64" -version = "0.0.21" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "base64 ~ (uutils) decode/encode input (base64-encoding)" diff --git a/src/uu/basename/Cargo.toml b/src/uu/basename/Cargo.toml index 878a5d9e102..e74ad761635 100644 --- a/src/uu/basename/Cargo.toml +++ b/src/uu/basename/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_basename" -version = "0.0.21" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "basename ~ (uutils) display PATHNAME with leading directory components removed" diff --git a/src/uu/basenc/Cargo.toml b/src/uu/basenc/Cargo.toml index 92912e5dfbf..7f418844070 100644 --- a/src/uu/basenc/Cargo.toml +++ b/src/uu/basenc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_basenc" -version = "0.0.21" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "basenc ~ (uutils) decode/encode input" diff --git a/src/uu/cat/Cargo.toml b/src/uu/cat/Cargo.toml index 166e1823b58..33488cd072d 100644 --- a/src/uu/cat/Cargo.toml +++ b/src/uu/cat/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_cat" -version = "0.0.21" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "cat ~ (uutils) concatenate and display input" diff --git a/src/uu/chcon/Cargo.toml b/src/uu/chcon/Cargo.toml index c83c8abfc14..07bd73f4b4a 100644 --- a/src/uu/chcon/Cargo.toml +++ b/src/uu/chcon/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_chcon" -version = "0.0.21" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "chcon ~ (uutils) change file security context" diff --git a/src/uu/chgrp/Cargo.toml b/src/uu/chgrp/Cargo.toml index 7f43e846625..9591e13651a 100644 --- a/src/uu/chgrp/Cargo.toml +++ b/src/uu/chgrp/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_chgrp" -version = "0.0.21" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "chgrp ~ (uutils) change the group ownership of FILE" diff --git a/src/uu/chmod/Cargo.toml b/src/uu/chmod/Cargo.toml index 2a9c73d9db3..d10d0e08dac 100644 --- a/src/uu/chmod/Cargo.toml +++ b/src/uu/chmod/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_chmod" -version = "0.0.21" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "chmod ~ (uutils) change mode of FILE" diff --git a/src/uu/chown/Cargo.toml b/src/uu/chown/Cargo.toml index 82b32875d65..bbd75db0321 100644 --- a/src/uu/chown/Cargo.toml +++ b/src/uu/chown/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_chown" -version = "0.0.21" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "chown ~ (uutils) change the ownership of FILE" diff --git a/src/uu/chroot/Cargo.toml b/src/uu/chroot/Cargo.toml index 3c836add869..847b18ef5e2 100644 --- a/src/uu/chroot/Cargo.toml +++ b/src/uu/chroot/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_chroot" -version = "0.0.21" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "chroot ~ (uutils) run COMMAND under a new root directory" diff --git a/src/uu/cksum/Cargo.toml b/src/uu/cksum/Cargo.toml index 49a881f706f..e0d54edb9fc 100644 --- a/src/uu/cksum/Cargo.toml +++ b/src/uu/cksum/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_cksum" -version = "0.0.21" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "cksum ~ (uutils) display CRC and size of input" diff --git a/src/uu/comm/Cargo.toml b/src/uu/comm/Cargo.toml index e4a1dac82fa..07430f9d9f1 100644 --- a/src/uu/comm/Cargo.toml +++ b/src/uu/comm/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_comm" -version = "0.0.21" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "comm ~ (uutils) compare sorted inputs" diff --git a/src/uu/cp/Cargo.toml b/src/uu/cp/Cargo.toml index 177525ff5f2..49ba75ca089 100644 --- a/src/uu/cp/Cargo.toml +++ b/src/uu/cp/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_cp" -version = "0.0.21" +version = "0.0.22" authors = [ "Jordy Dickinson ", "Joshua S. Miller ", diff --git a/src/uu/csplit/Cargo.toml b/src/uu/csplit/Cargo.toml index ba051ff8ab4..a30ebc4f67b 100644 --- a/src/uu/csplit/Cargo.toml +++ b/src/uu/csplit/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_csplit" -version = "0.0.21" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "csplit ~ (uutils) Output pieces of FILE separated by PATTERN(s) to files 'xx00', 'xx01', ..., and output byte counts of each piece to standard output" diff --git a/src/uu/cut/Cargo.toml b/src/uu/cut/Cargo.toml index a55ce7d5862..695777a72c1 100644 --- a/src/uu/cut/Cargo.toml +++ b/src/uu/cut/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_cut" -version = "0.0.21" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "cut ~ (uutils) display byte/field columns of input lines" diff --git a/src/uu/date/Cargo.toml b/src/uu/date/Cargo.toml index 7590b0b746c..cd860ce581b 100644 --- a/src/uu/date/Cargo.toml +++ b/src/uu/date/Cargo.toml @@ -1,7 +1,7 @@ # spell-checker:ignore datetime [package] name = "uu_date" -version = "0.0.21" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "date ~ (uutils) display or set the current time" diff --git a/src/uu/dd/Cargo.toml b/src/uu/dd/Cargo.toml index 482128e1d08..f56e46eaa21 100644 --- a/src/uu/dd/Cargo.toml +++ b/src/uu/dd/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_dd" -version = "0.0.21" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "dd ~ (uutils) copy and convert files" diff --git a/src/uu/df/Cargo.toml b/src/uu/df/Cargo.toml index b547a35d5ee..2aae7bd551f 100644 --- a/src/uu/df/Cargo.toml +++ b/src/uu/df/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_df" -version = "0.0.21" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "df ~ (uutils) display file system information" diff --git a/src/uu/dir/Cargo.toml b/src/uu/dir/Cargo.toml index 2a709a80465..0bca7bb9d80 100644 --- a/src/uu/dir/Cargo.toml +++ b/src/uu/dir/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_dir" -version = "0.0.21" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "shortcut to ls -C -b" diff --git a/src/uu/dircolors/Cargo.toml b/src/uu/dircolors/Cargo.toml index 9248f5ea12f..a4a101326a8 100644 --- a/src/uu/dircolors/Cargo.toml +++ b/src/uu/dircolors/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_dircolors" -version = "0.0.21" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "dircolors ~ (uutils) display commands to set LS_COLORS" diff --git a/src/uu/dirname/Cargo.toml b/src/uu/dirname/Cargo.toml index 6de444499db..67806986cc9 100644 --- a/src/uu/dirname/Cargo.toml +++ b/src/uu/dirname/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_dirname" -version = "0.0.21" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "dirname ~ (uutils) display parent directory of PATHNAME" diff --git a/src/uu/du/Cargo.toml b/src/uu/du/Cargo.toml index c87595db6ca..158fa97864f 100644 --- a/src/uu/du/Cargo.toml +++ b/src/uu/du/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_du" -version = "0.0.21" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "du ~ (uutils) display disk usage" diff --git a/src/uu/echo/Cargo.toml b/src/uu/echo/Cargo.toml index 5d27bae1b64..92d5d4924fc 100644 --- a/src/uu/echo/Cargo.toml +++ b/src/uu/echo/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_echo" -version = "0.0.21" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "echo ~ (uutils) display TEXT" diff --git a/src/uu/env/Cargo.toml b/src/uu/env/Cargo.toml index b2854edbf05..78b133733ab 100644 --- a/src/uu/env/Cargo.toml +++ b/src/uu/env/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_env" -version = "0.0.21" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "env ~ (uutils) set each NAME to VALUE in the environment and run COMMAND" diff --git a/src/uu/expand/Cargo.toml b/src/uu/expand/Cargo.toml index 58647d8e9d5..c329e61b6b2 100644 --- a/src/uu/expand/Cargo.toml +++ b/src/uu/expand/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_expand" -version = "0.0.21" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "expand ~ (uutils) convert input tabs to spaces" diff --git a/src/uu/expr/Cargo.toml b/src/uu/expr/Cargo.toml index ae31c66901a..3f0b5ca76ad 100644 --- a/src/uu/expr/Cargo.toml +++ b/src/uu/expr/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_expr" -version = "0.0.21" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "expr ~ (uutils) display the value of EXPRESSION" diff --git a/src/uu/factor/Cargo.toml b/src/uu/factor/Cargo.toml index 57b5e9d54ad..8ecee86c577 100644 --- a/src/uu/factor/Cargo.toml +++ b/src/uu/factor/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_factor" -version = "0.0.21" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "factor ~ (uutils) display the prime factors of each NUMBER" diff --git a/src/uu/false/Cargo.toml b/src/uu/false/Cargo.toml index 80f3553c0ef..3b1ca97f968 100644 --- a/src/uu/false/Cargo.toml +++ b/src/uu/false/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_false" -version = "0.0.21" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "false ~ (uutils) do nothing and fail" diff --git a/src/uu/fmt/Cargo.toml b/src/uu/fmt/Cargo.toml index 5b181937bc3..c49f52741b4 100644 --- a/src/uu/fmt/Cargo.toml +++ b/src/uu/fmt/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_fmt" -version = "0.0.21" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "fmt ~ (uutils) reformat each paragraph of input" diff --git a/src/uu/fold/Cargo.toml b/src/uu/fold/Cargo.toml index ff2f2f6d564..af36f0466ee 100644 --- a/src/uu/fold/Cargo.toml +++ b/src/uu/fold/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_fold" -version = "0.0.21" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "fold ~ (uutils) wrap each line of input" diff --git a/src/uu/groups/Cargo.toml b/src/uu/groups/Cargo.toml index d503d3e83f6..5f786acfd9f 100644 --- a/src/uu/groups/Cargo.toml +++ b/src/uu/groups/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_groups" -version = "0.0.21" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "groups ~ (uutils) display group memberships for USERNAME" diff --git a/src/uu/hashsum/Cargo.toml b/src/uu/hashsum/Cargo.toml index 1b075fb4871..23b60a7014a 100644 --- a/src/uu/hashsum/Cargo.toml +++ b/src/uu/hashsum/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_hashsum" -version = "0.0.21" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "hashsum ~ (uutils) display or check input digests" diff --git a/src/uu/head/Cargo.toml b/src/uu/head/Cargo.toml index bdbe45dc185..7c73f3941a7 100644 --- a/src/uu/head/Cargo.toml +++ b/src/uu/head/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_head" -version = "0.0.21" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "head ~ (uutils) display the first lines of input" diff --git a/src/uu/hostid/Cargo.toml b/src/uu/hostid/Cargo.toml index f34fee820a5..2e6dcc33952 100644 --- a/src/uu/hostid/Cargo.toml +++ b/src/uu/hostid/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_hostid" -version = "0.0.21" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "hostid ~ (uutils) display the numeric identifier of the current host" diff --git a/src/uu/hostname/Cargo.toml b/src/uu/hostname/Cargo.toml index 8e36e672a80..49cb14bf2b1 100644 --- a/src/uu/hostname/Cargo.toml +++ b/src/uu/hostname/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_hostname" -version = "0.0.21" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "hostname ~ (uutils) display or set the host name of the current host" diff --git a/src/uu/id/Cargo.toml b/src/uu/id/Cargo.toml index 77cda20be89..bf1eaf44ca5 100644 --- a/src/uu/id/Cargo.toml +++ b/src/uu/id/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_id" -version = "0.0.21" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "id ~ (uutils) display user and group information for USER" diff --git a/src/uu/install/Cargo.toml b/src/uu/install/Cargo.toml index e66cbeb72c7..b5353eace6a 100644 --- a/src/uu/install/Cargo.toml +++ b/src/uu/install/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_install" -version = "0.0.21" +version = "0.0.22" authors = ["Ben Eills ", "uutils developers"] license = "MIT" description = "install ~ (uutils) copy files from SOURCE to DESTINATION (with specified attributes)" diff --git a/src/uu/join/Cargo.toml b/src/uu/join/Cargo.toml index 971f8e4552b..c52f66de44e 100644 --- a/src/uu/join/Cargo.toml +++ b/src/uu/join/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_join" -version = "0.0.21" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "join ~ (uutils) merge lines from inputs with matching join fields" diff --git a/src/uu/kill/Cargo.toml b/src/uu/kill/Cargo.toml index a675f00eda3..a606c81e745 100644 --- a/src/uu/kill/Cargo.toml +++ b/src/uu/kill/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_kill" -version = "0.0.21" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "kill ~ (uutils) send a signal to a process" diff --git a/src/uu/link/Cargo.toml b/src/uu/link/Cargo.toml index 2406476cabc..c79ca604199 100644 --- a/src/uu/link/Cargo.toml +++ b/src/uu/link/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_link" -version = "0.0.21" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "link ~ (uutils) create a hard (file system) link to FILE" diff --git a/src/uu/ln/Cargo.toml b/src/uu/ln/Cargo.toml index 6488963cce3..e9f0e6eb749 100644 --- a/src/uu/ln/Cargo.toml +++ b/src/uu/ln/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_ln" -version = "0.0.21" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "ln ~ (uutils) create a (file system) link to TARGET" diff --git a/src/uu/logname/Cargo.toml b/src/uu/logname/Cargo.toml index 29116455b40..41cbcf73b03 100644 --- a/src/uu/logname/Cargo.toml +++ b/src/uu/logname/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_logname" -version = "0.0.21" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "logname ~ (uutils) display the login name of the current user" diff --git a/src/uu/ls/Cargo.toml b/src/uu/ls/Cargo.toml index 4394865caba..64d1cf1364d 100644 --- a/src/uu/ls/Cargo.toml +++ b/src/uu/ls/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_ls" -version = "0.0.21" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "ls ~ (uutils) display directory contents" diff --git a/src/uu/mkdir/Cargo.toml b/src/uu/mkdir/Cargo.toml index c1a6ce2a863..c0d60dc9d50 100644 --- a/src/uu/mkdir/Cargo.toml +++ b/src/uu/mkdir/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_mkdir" -version = "0.0.21" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "mkdir ~ (uutils) create DIRECTORY" diff --git a/src/uu/mkfifo/Cargo.toml b/src/uu/mkfifo/Cargo.toml index 0c28f52de79..285fc2b3429 100644 --- a/src/uu/mkfifo/Cargo.toml +++ b/src/uu/mkfifo/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_mkfifo" -version = "0.0.21" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "mkfifo ~ (uutils) create FIFOs (named pipes)" diff --git a/src/uu/mknod/Cargo.toml b/src/uu/mknod/Cargo.toml index a230bec74ce..2e660171473 100644 --- a/src/uu/mknod/Cargo.toml +++ b/src/uu/mknod/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_mknod" -version = "0.0.21" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "mknod ~ (uutils) create special file NAME of TYPE" diff --git a/src/uu/mktemp/Cargo.toml b/src/uu/mktemp/Cargo.toml index 5dbc8492a0b..8d8a885b158 100644 --- a/src/uu/mktemp/Cargo.toml +++ b/src/uu/mktemp/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_mktemp" -version = "0.0.21" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "mktemp ~ (uutils) create and display a temporary file or directory from TEMPLATE" diff --git a/src/uu/more/Cargo.toml b/src/uu/more/Cargo.toml index bc0f2521794..5614a0495a3 100644 --- a/src/uu/more/Cargo.toml +++ b/src/uu/more/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_more" -version = "0.0.21" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "more ~ (uutils) input perusal filter" diff --git a/src/uu/mv/Cargo.toml b/src/uu/mv/Cargo.toml index b7fcf2d109b..0be31ad7254 100644 --- a/src/uu/mv/Cargo.toml +++ b/src/uu/mv/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_mv" -version = "0.0.21" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "mv ~ (uutils) move (rename) SOURCE to DESTINATION" diff --git a/src/uu/nice/Cargo.toml b/src/uu/nice/Cargo.toml index 65e1520d1b3..465b7b76595 100644 --- a/src/uu/nice/Cargo.toml +++ b/src/uu/nice/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_nice" -version = "0.0.21" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "nice ~ (uutils) run PROGRAM with modified scheduling priority" diff --git a/src/uu/nl/Cargo.toml b/src/uu/nl/Cargo.toml index db2bfacbd70..003e1791385 100644 --- a/src/uu/nl/Cargo.toml +++ b/src/uu/nl/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_nl" -version = "0.0.21" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "nl ~ (uutils) display input with added line numbers" diff --git a/src/uu/nohup/Cargo.toml b/src/uu/nohup/Cargo.toml index 9fbf125bf5b..41c98982af0 100644 --- a/src/uu/nohup/Cargo.toml +++ b/src/uu/nohup/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_nohup" -version = "0.0.21" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "nohup ~ (uutils) run COMMAND, ignoring hangup signals" diff --git a/src/uu/nproc/Cargo.toml b/src/uu/nproc/Cargo.toml index b6052de2ed3..0acc0e95113 100644 --- a/src/uu/nproc/Cargo.toml +++ b/src/uu/nproc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_nproc" -version = "0.0.21" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "nproc ~ (uutils) display the number of processing units available" diff --git a/src/uu/numfmt/Cargo.toml b/src/uu/numfmt/Cargo.toml index 8fbf886a241..cf2f0864606 100644 --- a/src/uu/numfmt/Cargo.toml +++ b/src/uu/numfmt/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_numfmt" -version = "0.0.21" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "numfmt ~ (uutils) reformat NUMBER" diff --git a/src/uu/od/Cargo.toml b/src/uu/od/Cargo.toml index d369456f32a..b7799e05200 100644 --- a/src/uu/od/Cargo.toml +++ b/src/uu/od/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_od" -version = "0.0.21" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "od ~ (uutils) display formatted representation of input" diff --git a/src/uu/paste/Cargo.toml b/src/uu/paste/Cargo.toml index d93d4cbb6b9..dd030478c09 100644 --- a/src/uu/paste/Cargo.toml +++ b/src/uu/paste/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_paste" -version = "0.0.21" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "paste ~ (uutils) merge lines from inputs" diff --git a/src/uu/pathchk/Cargo.toml b/src/uu/pathchk/Cargo.toml index f62c027c4fd..708ae2afbaf 100644 --- a/src/uu/pathchk/Cargo.toml +++ b/src/uu/pathchk/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_pathchk" -version = "0.0.21" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "pathchk ~ (uutils) diagnose invalid or non-portable PATHNAME" diff --git a/src/uu/pinky/Cargo.toml b/src/uu/pinky/Cargo.toml index f6d291e26e9..c4739e7fcb6 100644 --- a/src/uu/pinky/Cargo.toml +++ b/src/uu/pinky/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_pinky" -version = "0.0.21" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "pinky ~ (uutils) display user information" diff --git a/src/uu/pr/Cargo.toml b/src/uu/pr/Cargo.toml index 4453d1cbaa6..9a12e10f5bf 100644 --- a/src/uu/pr/Cargo.toml +++ b/src/uu/pr/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_pr" -version = "0.0.21" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "pr ~ (uutils) convert text files for printing" diff --git a/src/uu/printenv/Cargo.toml b/src/uu/printenv/Cargo.toml index 72965811011..64435127f6e 100644 --- a/src/uu/printenv/Cargo.toml +++ b/src/uu/printenv/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_printenv" -version = "0.0.21" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "printenv ~ (uutils) display value of environment VAR" diff --git a/src/uu/printf/Cargo.toml b/src/uu/printf/Cargo.toml index d55d3732a3a..a4fd773da28 100644 --- a/src/uu/printf/Cargo.toml +++ b/src/uu/printf/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_printf" -version = "0.0.21" +version = "0.0.22" authors = ["Nathan Ross", "uutils developers"] license = "MIT" description = "printf ~ (uutils) FORMAT and display ARGUMENTS" diff --git a/src/uu/ptx/Cargo.toml b/src/uu/ptx/Cargo.toml index 96d635caff6..ea241600bce 100644 --- a/src/uu/ptx/Cargo.toml +++ b/src/uu/ptx/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_ptx" -version = "0.0.21" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "ptx ~ (uutils) display a permuted index of input" diff --git a/src/uu/pwd/Cargo.toml b/src/uu/pwd/Cargo.toml index 1567a935f9a..5f15245b5d4 100644 --- a/src/uu/pwd/Cargo.toml +++ b/src/uu/pwd/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_pwd" -version = "0.0.21" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "pwd ~ (uutils) display current working directory" diff --git a/src/uu/readlink/Cargo.toml b/src/uu/readlink/Cargo.toml index 309d4079429..a696abdfaa2 100644 --- a/src/uu/readlink/Cargo.toml +++ b/src/uu/readlink/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_readlink" -version = "0.0.21" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "readlink ~ (uutils) display resolved path of PATHNAME" diff --git a/src/uu/realpath/Cargo.toml b/src/uu/realpath/Cargo.toml index e458bf89d13..6d207275322 100644 --- a/src/uu/realpath/Cargo.toml +++ b/src/uu/realpath/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_realpath" -version = "0.0.21" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "realpath ~ (uutils) display resolved absolute path of PATHNAME" diff --git a/src/uu/rm/Cargo.toml b/src/uu/rm/Cargo.toml index 87fccbfa875..a1b3999d4d4 100644 --- a/src/uu/rm/Cargo.toml +++ b/src/uu/rm/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_rm" -version = "0.0.21" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "rm ~ (uutils) remove PATHNAME" diff --git a/src/uu/rmdir/Cargo.toml b/src/uu/rmdir/Cargo.toml index 41d2f7432d1..8288309f258 100644 --- a/src/uu/rmdir/Cargo.toml +++ b/src/uu/rmdir/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_rmdir" -version = "0.0.21" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "rmdir ~ (uutils) remove empty DIRECTORY" diff --git a/src/uu/runcon/Cargo.toml b/src/uu/runcon/Cargo.toml index ac4ec54d5f0..04d3aae7139 100644 --- a/src/uu/runcon/Cargo.toml +++ b/src/uu/runcon/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_runcon" -version = "0.0.21" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "runcon ~ (uutils) run command with specified security context" diff --git a/src/uu/seq/Cargo.toml b/src/uu/seq/Cargo.toml index f25d995a2d6..b8a5c084cde 100644 --- a/src/uu/seq/Cargo.toml +++ b/src/uu/seq/Cargo.toml @@ -1,7 +1,7 @@ # spell-checker:ignore bigdecimal [package] name = "uu_seq" -version = "0.0.21" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "seq ~ (uutils) display a sequence of numbers" diff --git a/src/uu/shred/Cargo.toml b/src/uu/shred/Cargo.toml index 70045eaf07f..0e7ad2ad317 100644 --- a/src/uu/shred/Cargo.toml +++ b/src/uu/shred/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_shred" -version = "0.0.21" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "shred ~ (uutils) hide former FILE contents with repeated overwrites" diff --git a/src/uu/shuf/Cargo.toml b/src/uu/shuf/Cargo.toml index 2f0466b86fa..6a86c6d9858 100644 --- a/src/uu/shuf/Cargo.toml +++ b/src/uu/shuf/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_shuf" -version = "0.0.21" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "shuf ~ (uutils) display random permutations of input lines" diff --git a/src/uu/sleep/Cargo.toml b/src/uu/sleep/Cargo.toml index 454f2297ed5..6ca686c78ce 100644 --- a/src/uu/sleep/Cargo.toml +++ b/src/uu/sleep/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_sleep" -version = "0.0.21" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "sleep ~ (uutils) pause for DURATION" diff --git a/src/uu/sort/Cargo.toml b/src/uu/sort/Cargo.toml index 981e7164144..0cbafc5cfac 100644 --- a/src/uu/sort/Cargo.toml +++ b/src/uu/sort/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_sort" -version = "0.0.21" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "sort ~ (uutils) sort input lines" diff --git a/src/uu/split/Cargo.toml b/src/uu/split/Cargo.toml index fb53914e291..c5a34847324 100644 --- a/src/uu/split/Cargo.toml +++ b/src/uu/split/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_split" -version = "0.0.21" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "split ~ (uutils) split input into output files" diff --git a/src/uu/stat/Cargo.toml b/src/uu/stat/Cargo.toml index f258aca1135..2974562f352 100644 --- a/src/uu/stat/Cargo.toml +++ b/src/uu/stat/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_stat" -version = "0.0.21" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "stat ~ (uutils) display FILE status" diff --git a/src/uu/stdbuf/Cargo.toml b/src/uu/stdbuf/Cargo.toml index f6101490715..8168733affe 100644 --- a/src/uu/stdbuf/Cargo.toml +++ b/src/uu/stdbuf/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_stdbuf" -version = "0.0.21" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "stdbuf ~ (uutils) run COMMAND with modified standard stream buffering" @@ -20,7 +20,7 @@ tempfile = { workspace = true } uucore = { workspace = true } [build-dependencies] -libstdbuf = { version = "0.0.21", package = "uu_stdbuf_libstdbuf", path = "src/libstdbuf" } +libstdbuf = { version = "0.0.22", package = "uu_stdbuf_libstdbuf", path = "src/libstdbuf" } [[bin]] name = "stdbuf" diff --git a/src/uu/stdbuf/src/libstdbuf/Cargo.toml b/src/uu/stdbuf/src/libstdbuf/Cargo.toml index 8061c61a642..5fdfa27e035 100644 --- a/src/uu/stdbuf/src/libstdbuf/Cargo.toml +++ b/src/uu/stdbuf/src/libstdbuf/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_stdbuf_libstdbuf" -version = "0.0.21" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "stdbuf/libstdbuf ~ (uutils); dynamic library required for stdbuf" diff --git a/src/uu/stty/Cargo.toml b/src/uu/stty/Cargo.toml index eb86ae68102..965de5cd885 100644 --- a/src/uu/stty/Cargo.toml +++ b/src/uu/stty/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_stty" -version = "0.0.21" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "stty ~ (uutils) print or change terminal characteristics" diff --git a/src/uu/sum/Cargo.toml b/src/uu/sum/Cargo.toml index aa722c69433..58fa7a08d4c 100644 --- a/src/uu/sum/Cargo.toml +++ b/src/uu/sum/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_sum" -version = "0.0.21" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "sum ~ (uutils) display checksum and block counts for input" diff --git a/src/uu/sync/Cargo.toml b/src/uu/sync/Cargo.toml index b74e076e509..2367561f873 100644 --- a/src/uu/sync/Cargo.toml +++ b/src/uu/sync/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_sync" -version = "0.0.21" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "sync ~ (uutils) synchronize cache writes to storage" diff --git a/src/uu/tac/Cargo.toml b/src/uu/tac/Cargo.toml index 385ef37c89b..1716a6d0363 100644 --- a/src/uu/tac/Cargo.toml +++ b/src/uu/tac/Cargo.toml @@ -2,7 +2,7 @@ [package] name = "uu_tac" -version = "0.0.21" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "tac ~ (uutils) concatenate and display input lines in reverse order" diff --git a/src/uu/tail/Cargo.toml b/src/uu/tail/Cargo.toml index 5ff532e60fc..66775c8d9d8 100644 --- a/src/uu/tail/Cargo.toml +++ b/src/uu/tail/Cargo.toml @@ -1,7 +1,7 @@ # spell-checker:ignore (libs) kqueue fundu [package] name = "uu_tail" -version = "0.0.21" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "tail ~ (uutils) display the last lines of input" diff --git a/src/uu/tee/Cargo.toml b/src/uu/tee/Cargo.toml index 597299fc7d4..787c0cb326f 100644 --- a/src/uu/tee/Cargo.toml +++ b/src/uu/tee/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_tee" -version = "0.0.21" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "tee ~ (uutils) display input and copy to FILE" diff --git a/src/uu/test/Cargo.toml b/src/uu/test/Cargo.toml index fc70677a6c2..696fa366214 100644 --- a/src/uu/test/Cargo.toml +++ b/src/uu/test/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_test" -version = "0.0.21" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "test ~ (uutils) evaluate comparison and file type expressions" diff --git a/src/uu/timeout/Cargo.toml b/src/uu/timeout/Cargo.toml index 949ac9da5f7..b6cb700a47f 100644 --- a/src/uu/timeout/Cargo.toml +++ b/src/uu/timeout/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_timeout" -version = "0.0.21" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "timeout ~ (uutils) run COMMAND with a DURATION time limit" diff --git a/src/uu/touch/Cargo.toml b/src/uu/touch/Cargo.toml index 54682e1ddf4..b44e8cf69ff 100644 --- a/src/uu/touch/Cargo.toml +++ b/src/uu/touch/Cargo.toml @@ -1,7 +1,7 @@ # spell-checker:ignore datetime [package] name = "uu_touch" -version = "0.0.21" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "touch ~ (uutils) change FILE timestamps" diff --git a/src/uu/tr/Cargo.toml b/src/uu/tr/Cargo.toml index 065fdab5274..c7e7bd60644 100644 --- a/src/uu/tr/Cargo.toml +++ b/src/uu/tr/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_tr" -version = "0.0.21" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "tr ~ (uutils) translate characters within input and display" diff --git a/src/uu/true/Cargo.toml b/src/uu/true/Cargo.toml index 432dfa54e91..ec6c5d2f909 100644 --- a/src/uu/true/Cargo.toml +++ b/src/uu/true/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_true" -version = "0.0.21" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "true ~ (uutils) do nothing and succeed" diff --git a/src/uu/truncate/Cargo.toml b/src/uu/truncate/Cargo.toml index fbabe1fcd4f..a9cc179a67f 100644 --- a/src/uu/truncate/Cargo.toml +++ b/src/uu/truncate/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_truncate" -version = "0.0.21" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "truncate ~ (uutils) truncate (or extend) FILE to SIZE" diff --git a/src/uu/tsort/Cargo.toml b/src/uu/tsort/Cargo.toml index 2f276f98eea..f72aeeda083 100644 --- a/src/uu/tsort/Cargo.toml +++ b/src/uu/tsort/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_tsort" -version = "0.0.21" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "tsort ~ (uutils) topologically sort input (partially ordered) pairs" diff --git a/src/uu/tty/Cargo.toml b/src/uu/tty/Cargo.toml index 917d04c106a..66d4b42c795 100644 --- a/src/uu/tty/Cargo.toml +++ b/src/uu/tty/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_tty" -version = "0.0.21" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "tty ~ (uutils) display the name of the terminal connected to standard input" diff --git a/src/uu/uname/Cargo.toml b/src/uu/uname/Cargo.toml index ccb3651ec63..10363b63cb0 100644 --- a/src/uu/uname/Cargo.toml +++ b/src/uu/uname/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_uname" -version = "0.0.21" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "uname ~ (uutils) display system information" diff --git a/src/uu/unexpand/Cargo.toml b/src/uu/unexpand/Cargo.toml index 6a12d48ab6b..bab5b4e915b 100644 --- a/src/uu/unexpand/Cargo.toml +++ b/src/uu/unexpand/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_unexpand" -version = "0.0.21" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "unexpand ~ (uutils) convert input spaces to tabs" diff --git a/src/uu/uniq/Cargo.toml b/src/uu/uniq/Cargo.toml index 0afcffc118b..2fb0552ce7b 100644 --- a/src/uu/uniq/Cargo.toml +++ b/src/uu/uniq/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_uniq" -version = "0.0.21" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "uniq ~ (uutils) filter identical adjacent lines from input" diff --git a/src/uu/unlink/Cargo.toml b/src/uu/unlink/Cargo.toml index ce86b81d8d1..aae56bf0bff 100644 --- a/src/uu/unlink/Cargo.toml +++ b/src/uu/unlink/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_unlink" -version = "0.0.21" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "unlink ~ (uutils) remove a (file system) link to FILE" diff --git a/src/uu/uptime/Cargo.toml b/src/uu/uptime/Cargo.toml index 7bef7cc051d..f5fa083f9f5 100644 --- a/src/uu/uptime/Cargo.toml +++ b/src/uu/uptime/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_uptime" -version = "0.0.21" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "uptime ~ (uutils) display dynamic system information" diff --git a/src/uu/users/Cargo.toml b/src/uu/users/Cargo.toml index 4701c694f46..d894ff4ba66 100644 --- a/src/uu/users/Cargo.toml +++ b/src/uu/users/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_users" -version = "0.0.21" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "users ~ (uutils) display names of currently logged-in users" diff --git a/src/uu/vdir/Cargo.toml b/src/uu/vdir/Cargo.toml index 99bcf383b98..b63f8c9eafc 100644 --- a/src/uu/vdir/Cargo.toml +++ b/src/uu/vdir/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_vdir" -version = "0.0.21" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "shortcut to ls -l -b" diff --git a/src/uu/wc/Cargo.toml b/src/uu/wc/Cargo.toml index 5712fbd8339..6a233c1ade3 100644 --- a/src/uu/wc/Cargo.toml +++ b/src/uu/wc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_wc" -version = "0.0.21" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "wc ~ (uutils) display newline, word, and byte counts for input" diff --git a/src/uu/who/Cargo.toml b/src/uu/who/Cargo.toml index b8b90b2ef31..fdfc0589757 100644 --- a/src/uu/who/Cargo.toml +++ b/src/uu/who/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_who" -version = "0.0.21" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "who ~ (uutils) display information about currently logged-in users" diff --git a/src/uu/whoami/Cargo.toml b/src/uu/whoami/Cargo.toml index 331755a9651..59d011209fd 100644 --- a/src/uu/whoami/Cargo.toml +++ b/src/uu/whoami/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_whoami" -version = "0.0.21" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "whoami ~ (uutils) display user name of current effective user ID" diff --git a/src/uu/yes/Cargo.toml b/src/uu/yes/Cargo.toml index eff09155119..c36c2b1897d 100644 --- a/src/uu/yes/Cargo.toml +++ b/src/uu/yes/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_yes" -version = "0.0.21" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "yes ~ (uutils) repeatedly display a line with STRING (or 'y')" diff --git a/src/uucore/Cargo.toml b/src/uucore/Cargo.toml index 5e555da4da4..8ce252dc561 100644 --- a/src/uucore/Cargo.toml +++ b/src/uucore/Cargo.toml @@ -2,7 +2,7 @@ [package] name = "uucore" -version = "0.0.21" +version = "0.0.22" authors = ["uutils developers"] license = "MIT" description = "uutils ~ 'core' uutils code library (cross-platform)" diff --git a/src/uucore_procs/Cargo.toml b/src/uucore_procs/Cargo.toml index 1ad4023f9b1..55d88096135 100644 --- a/src/uucore_procs/Cargo.toml +++ b/src/uucore_procs/Cargo.toml @@ -1,7 +1,7 @@ # spell-checker:ignore uuhelp [package] name = "uucore_procs" -version = "0.0.21" +version = "0.0.22" authors = ["Roy Ivy III "] license = "MIT" description = "uutils ~ 'uucore' proc-macros" @@ -19,4 +19,4 @@ proc-macro = true [dependencies] proc-macro2 = "1.0" quote = "1.0" -uuhelp_parser = { path = "../uuhelp_parser", version = "0.0.21" } +uuhelp_parser = { path = "../uuhelp_parser", version = "0.0.22" } diff --git a/src/uuhelp_parser/Cargo.toml b/src/uuhelp_parser/Cargo.toml index fdff271b053..007f1ebb2c2 100644 --- a/src/uuhelp_parser/Cargo.toml +++ b/src/uuhelp_parser/Cargo.toml @@ -1,7 +1,7 @@ # spell-checker:ignore uuhelp [package] name = "uuhelp_parser" -version = "0.0.21" +version = "0.0.22" edition = "2021" license = "MIT" description = "A collection of functions to parse the markdown code of help files" diff --git a/util/update-version.sh b/util/update-version.sh index 6738f19b66d..59bdb4d751a 100755 --- a/util/update-version.sh +++ b/util/update-version.sh @@ -14,8 +14,8 @@ # 7) Run util/publish.sh --do-it # 8) In some cases, you might have to fix dependencies and run import -FROM="0.0.20" -TO="0.0.21" +FROM="0.0.21" +TO="0.0.22" PROGS=$(ls -1d src/uu/*/Cargo.toml src/uu/stdbuf/src/libstdbuf/Cargo.toml src/uucore/Cargo.toml Cargo.toml) From fcd0817b3b4a2fd6e831c23aef400c7c018f84a3 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 15 Oct 2023 14:24:05 +0200 Subject: [PATCH 0231/2851] update of the release doc --- util/update-version.sh | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/util/update-version.sh b/util/update-version.sh index 6738f19b66d..86e7f329ba3 100755 --- a/util/update-version.sh +++ b/util/update-version.sh @@ -9,10 +9,13 @@ # 2) run it: sh util/update-version.sh # 3) Do a spot check with "git diff" # 4) cargo test --release --features unix -# 5) git commit -m "New release" +# 5) git commit -m "New release" (make sure it includes Cargo.lock) # 6) Run util/publish.sh in dry mode (it will fail as packages needs more recent version of uucore) # 7) Run util/publish.sh --do-it # 8) In some cases, you might have to fix dependencies and run import +# 9) Tag the release - "git tag 0.0.X && git push --tags" +# 10) Create the release on github https://github.com/uutils/coreutils/releases/new +# 11) Make sure we have good release notes FROM="0.0.20" TO="0.0.21" From c892c9346f945a8572a6b24bd7cb80625ec8a23f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 15 Oct 2023 17:59:54 +0000 Subject: [PATCH 0232/2851] fix(deps): update rust crate dns-lookup to 2.0.4 --- Cargo.lock | 4 ++-- src/uucore/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a54656d7c4a..4481d08d326 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -743,9 +743,9 @@ dependencies = [ [[package]] name = "dns-lookup" -version = "2.0.3" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d0fa3cd8dc96ada974e126a940d37d4079bbbe6a24aca15b1113c2f362441c5" +checksum = "e5766087c2235fec47fafa4cfecc81e494ee679d0fd4a59887ea0919bfb0e4fc" dependencies = [ "cfg-if", "libc", diff --git a/src/uucore/Cargo.toml b/src/uucore/Cargo.toml index 8ce252dc561..fb0b20338dc 100644 --- a/src/uucore/Cargo.toml +++ b/src/uucore/Cargo.toml @@ -20,7 +20,7 @@ path = "src/lib/lib.rs" [dependencies] clap = { workspace = true } uucore_procs = { workspace = true } -dns-lookup = { version = "2.0.3", optional = true } +dns-lookup = { version = "2.0.4", optional = true } dunce = { version = "1.0.4", optional = true } wild = "2.2" glob = { workspace = true } From 3a780453a9cc23b928baeaac3dc9588f067a6d5a Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Fri, 6 Oct 2023 21:25:24 +0200 Subject: [PATCH 0233/2851] join: remove a clippy::cognitive_complexity by moving some content into functions --- src/uu/join/src/join.rs | 102 +++++++++++++++++++++++++--------------- 1 file changed, 65 insertions(+), 37 deletions(-) diff --git a/src/uu/join/src/join.rs b/src/uu/join/src/join.rs index 71720f2cca9..7f6d1038f17 100644 --- a/src/uu/join/src/join.rs +++ b/src/uu/join/src/join.rs @@ -591,20 +591,38 @@ impl<'a> State<'a> { } } -#[uucore::main] -#[allow(clippy::cognitive_complexity)] -pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().try_get_matches_from(args)?; - - let keys = parse_field_number_option(matches.get_one::("j").map(|s| s.as_str()))?; - let key1 = parse_field_number_option(matches.get_one::("1").map(|s| s.as_str()))?; - let key2 = parse_field_number_option(matches.get_one::("2").map(|s| s.as_str()))?; +fn parse_separator(value_os: &OsString) -> UResult { + #[cfg(unix)] + let value = value_os.as_bytes(); + #[cfg(not(unix))] + let value = match value_os.to_str() { + Some(value) => value.as_bytes(), + None => { + return Err(USimpleError::new( + 1, + "unprintable field separators are only supported on unix-like platforms", + )); + } + }; + match value.len() { + 0 => Ok(Sep::Line), + 1 => Ok(Sep::Char(value[0])), + 2 if value[0] == b'\\' && value[1] == b'0' => Ok(Sep::Char(0)), + _ => Err(USimpleError::new( + 1, + format!("multi-character tab {}", value_os.to_string_lossy()), + )), + } +} - let mut settings = Settings::default(); +fn parse_print_settings(matches: &clap::ArgMatches) -> UResult<(bool, bool, bool)> { + let mut print_joined = true; + let mut print_unpaired1 = false; + let mut print_unpaired2 = false; let v_values = matches.get_many::("v"); if v_values.is_some() { - settings.print_joined = false; + print_joined = false; } let unpaired = v_values @@ -612,41 +630,42 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { .chain(matches.get_many("a").unwrap_or_default()); for file_num in unpaired { match parse_file_number(file_num)? { - FileNum::File1 => settings.print_unpaired1 = true, - FileNum::File2 => settings.print_unpaired2 = true, + FileNum::File1 => print_unpaired1 = true, + FileNum::File2 => print_unpaired2 = true, } } + Ok((print_joined, print_unpaired1, print_unpaired2)) +} + +fn get_and_parse_field_number(matches: &clap::ArgMatches, key: &str) -> UResult> { + let value = matches.get_one::(key).map(|s| s.as_str()); + parse_field_number_option(value) +} + +/// Parses the command-line arguments and constructs a `Settings` struct. +/// +/// This function takes the matches from the command-line arguments, processes them, +/// and returns a `Settings` struct that encapsulates the configuration for the program. +fn parse_settings(matches: &clap::ArgMatches) -> UResult { + let keys = get_and_parse_field_number(matches, "j")?; + let key1 = get_and_parse_field_number(matches, "1")?; + let key2 = get_and_parse_field_number(matches, "2")?; + + let (print_joined, print_unpaired1, print_unpaired2) = parse_print_settings(matches)?; + + let mut settings = Settings::default(); + + settings.print_joined = print_joined; + settings.print_unpaired1 = print_unpaired1; + settings.print_unpaired2 = print_unpaired2; + settings.ignore_case = matches.get_flag("i"); settings.key1 = get_field_number(keys, key1)?; settings.key2 = get_field_number(keys, key2)?; - if let Some(value_os) = matches.get_one::("t") { - #[cfg(unix)] - let value = value_os.as_bytes(); - #[cfg(not(unix))] - let value = match value_os.to_str() { - Some(value) => value.as_bytes(), - None => { - return Err(USimpleError::new( - 1, - "unprintable field separators are only supported on unix-like platforms", - )) - } - }; - settings.separator = match value.len() { - 0 => Sep::Line, - 1 => Sep::Char(value[0]), - 2 if value[0] == b'\\' && value[1] == b'0' => Sep::Char(0), - _ => { - return Err(USimpleError::new( - 1, - format!("multi-character tab {}", value_os.to_string_lossy()), - )) - } - }; + settings.separator = parse_separator(value_os)?; } - if let Some(format) = matches.get_one::("o") { if format == "auto" { settings.autoformat = true; @@ -677,6 +696,15 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { settings.line_ending = LineEnding::from_zero_flag(matches.get_flag("z")); + Ok(settings) +} + +#[uucore::main] +pub fn uumain(args: impl uucore::Args) -> UResult<()> { + let matches = uu_app().try_get_matches_from(args)?; + + let settings = parse_settings(&matches)?; + let file1 = matches.get_one::("file1").unwrap(); let file2 = matches.get_one::("file2").unwrap(); From 21aca7abd750b283eebe8928d35e407402f86fad Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Mon, 16 Oct 2023 09:48:20 +0200 Subject: [PATCH 0234/2851] doc: add a missing "a" --- DEVELOPMENT.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index 9cb81a88ce4..67b201e9cc3 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -6,7 +6,7 @@ For contributing rules and best practices please refer to [CONTRIBUTING.md](CONT ## Before you start -For this guide we assume that you already have GitHub account and have `git` and your favorite code editor or IDE installed and configured. +For this guide we assume that you already have a GitHub account and have `git` and your favorite code editor or IDE installed and configured. Before you start working on coreutils, please follow these steps: 1. Fork the [coreutils repository](https://github.com/uutils/coreutils) to your GitHub account. From f8436728dc1a5df064c21bba8cbe422bd74d2e06 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Mon, 16 Oct 2023 10:47:38 +0200 Subject: [PATCH 0235/2851] add field_reassign_with_default ignore Co-authored-by: Daniel Hofstetter --- src/uu/join/src/join.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/uu/join/src/join.rs b/src/uu/join/src/join.rs index 7f6d1038f17..a48ba3657bd 100644 --- a/src/uu/join/src/join.rs +++ b/src/uu/join/src/join.rs @@ -647,6 +647,7 @@ fn get_and_parse_field_number(matches: &clap::ArgMatches, key: &str) -> UResult< /// /// This function takes the matches from the command-line arguments, processes them, /// and returns a `Settings` struct that encapsulates the configuration for the program. +#[allow(clippy::field_reassign_with_default)] fn parse_settings(matches: &clap::ArgMatches) -> UResult { let keys = get_and_parse_field_number(matches, "j")?; let key1 = get_and_parse_field_number(matches, "1")?; From 3288c64b00703ac39accb4abec94a7bf45ef978d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 16 Oct 2023 16:33:17 +0000 Subject: [PATCH 0236/2851] chore(deps): update rust crate regex to 1.10.2 --- Cargo.lock | 8 ++++---- Cargo.toml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4481d08d326..dfa22ad2747 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1728,9 +1728,9 @@ checksum = "f1bfbf25d7eb88ddcbb1ec3d755d0634da8f7657b2cb8b74089121409ab8228f" [[package]] name = "regex" -version = "1.10.1" +version = "1.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aaac441002f822bc9705a681810a4dd2963094b9ca0ddc41cb963a4c189189ea" +checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" dependencies = [ "aho-corasick", "memchr", @@ -1740,9 +1740,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5011c7e263a695dc8ca064cddb722af1be54e517a280b12a5356f98366899e5d" +checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" dependencies = [ "aho-corasick", "memchr", diff --git a/Cargo.toml b/Cargo.toml index 39232b50d64..6b412488cd3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -309,7 +309,7 @@ rand = { version = "0.8", features = ["small_rng"] } rand_core = "0.6" rayon = "1.8" redox_syscall = "0.4" -regex = "1.10.1" +regex = "1.10.2" rstest = "0.18.2" rust-ini = "0.19.0" same-file = "1.0.6" From 7421c81a22cebd5ea80d2614766c195431b2089e Mon Sep 17 00:00:00 2001 From: Zhuoxun Yang Date: Tue, 17 Oct 2023 22:21:44 +0800 Subject: [PATCH 0237/2851] tests/expr: sort test cases --- tests/by-util/test_expr.rs | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/tests/by-util/test_expr.rs b/tests/by-util/test_expr.rs index d4cca43a28b..840e1f32576 100644 --- a/tests/by-util/test_expr.rs +++ b/tests/by-util/test_expr.rs @@ -152,14 +152,20 @@ fn test_and() { .succeeds() .stdout_only("foo\n"); - new_ucmd!().args(&["", "&", "1"]).run().stdout_is("0\n"); - - new_ucmd!().args(&["14", "&", "1"]).run().stdout_is("14\n"); + new_ucmd!() + .args(&["14", "&", "1"]) + .succeeds() + .stdout_only("14\n"); new_ucmd!() .args(&["-14", "&", "1"]) - .run() - .stdout_is("-14\n"); + .succeeds() + .stdout_only("-14\n"); + + new_ucmd!() + .args(&["-1", "&", "10", "/", "5"]) + .succeeds() + .stdout_only("-1\n"); new_ucmd!() .args(&["0", "&", "a", "/", "5"]) @@ -171,10 +177,7 @@ fn test_and() { .run() .stdout_only("0\n"); - new_ucmd!() - .args(&["-1", "&", "10", "/", "5"]) - .succeeds() - .stdout_only("-1\n"); + new_ucmd!().args(&["", "&", "1"]).run().stdout_only("0\n"); } #[test] From 04ab5b010860e290fa0b23b7a4cca47ce934e996 Mon Sep 17 00:00:00 2001 From: Zhuoxun Yang Date: Tue, 17 Oct 2023 22:26:19 +0800 Subject: [PATCH 0238/2851] tests/expr: add tests for "" --- tests/by-util/test_expr.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/by-util/test_expr.rs b/tests/by-util/test_expr.rs index 840e1f32576..28cfcf0ec90 100644 --- a/tests/by-util/test_expr.rs +++ b/tests/by-util/test_expr.rs @@ -143,6 +143,14 @@ fn test_or() { .args(&["12", "|", "9a", "+", "1"]) .succeeds() .stdout_only("12\n"); + + new_ucmd!().args(&["", "|", ""]).run().stdout_only("0\n"); + + new_ucmd!().args(&["", "|", "0"]).run().stdout_only("0\n"); + + new_ucmd!().args(&["", "|", "00"]).run().stdout_only("0\n"); + + new_ucmd!().args(&["", "|", "-0"]).run().stdout_only("0\n"); } #[test] @@ -178,6 +186,8 @@ fn test_and() { .stdout_only("0\n"); new_ucmd!().args(&["", "&", "1"]).run().stdout_only("0\n"); + + new_ucmd!().args(&["", "&", ""]).run().stdout_only("0\n"); } #[test] From 8a6722491746ff7ec805ea058a30de1236836754 Mon Sep 17 00:00:00 2001 From: Zhuoxun Yang Date: Tue, 17 Oct 2023 22:27:10 +0800 Subject: [PATCH 0239/2851] expr: add assert for `&` --- src/uu/expr/src/syntax_tree.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/uu/expr/src/syntax_tree.rs b/src/uu/expr/src/syntax_tree.rs index e0e786b3a3b..2cd2af0b125 100644 --- a/src/uu/expr/src/syntax_tree.rs +++ b/src/uu/expr/src/syntax_tree.rs @@ -470,6 +470,7 @@ fn infix_operator_or(values: &[String]) -> String { } fn infix_operator_and(values: &[String]) -> String { + assert!(values.len() == 2); if value_as_bool(&values[0]) && value_as_bool(&values[1]) { values[0].clone() } else { From d325a952ee1133eaa76cc3d1196a9364c2a539dc Mon Sep 17 00:00:00 2001 From: Zhuoxun Yang Date: Tue, 17 Oct 2023 22:27:47 +0800 Subject: [PATCH 0240/2851] expr: return "0" for `|` --- src/uu/expr/src/syntax_tree.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/uu/expr/src/syntax_tree.rs b/src/uu/expr/src/syntax_tree.rs index 2cd2af0b125..c55fb0bdc6a 100644 --- a/src/uu/expr/src/syntax_tree.rs +++ b/src/uu/expr/src/syntax_tree.rs @@ -464,8 +464,10 @@ fn infix_operator_or(values: &[String]) -> String { assert!(values.len() == 2); if value_as_bool(&values[0]) { values[0].clone() - } else { + } else if value_as_bool(&values[1]) { values[1].clone() + } else { + 0.to_string() } } From cb7479e823cf14329a93f6d93eed8acff8c5ac7f Mon Sep 17 00:00:00 2001 From: zhitkoff Date: Wed, 4 Oct 2023 21:26:29 -0400 Subject: [PATCH 0241/2851] uucore: implement SI suffixes R and Q --- src/uu/dd/src/parseargs.rs | 2 +- src/uu/df/src/blocks.rs | 6 +- src/uu/du/src/du.rs | 8 +- src/uu/head/src/parse.rs | 4 +- src/uu/ls/src/ls.rs | 4 +- src/uu/od/src/parse_nrofbytes.rs | 4 +- src/uu/shred/src/shred.rs | 4 +- src/uu/split/src/split.rs | 24 +- src/uu/stdbuf/src/stdbuf.rs | 4 +- src/uu/tail/src/args.rs | 4 +- src/uu/truncate/src/truncate.rs | 6 +- src/uucore/src/lib/parser/parse_size.rs | 307 +++++++++++++++--------- tests/by-util/test_head.rs | 8 +- tests/by-util/test_split.rs | 4 +- tests/by-util/test_truncate.rs | 2 +- 15 files changed, 244 insertions(+), 147 deletions(-) diff --git a/src/uu/dd/src/parseargs.rs b/src/uu/dd/src/parseargs.rs index 53fae1b4b40..0ff6e752c02 100644 --- a/src/uu/dd/src/parseargs.rs +++ b/src/uu/dd/src/parseargs.rs @@ -504,7 +504,7 @@ fn parse_bytes_no_x(full: &str, s: &str) -> Result { ..Default::default() }; let (num, multiplier) = match (s.find('c'), s.rfind('w'), s.rfind('b')) { - (None, None, None) => match parser.parse(s) { + (None, None, None) => match parser.parse_u64(s) { Ok(n) => (n, 1), Err(ParseSizeError::InvalidSuffix(_) | ParseSizeError::ParseFailure(_)) => { return Err(ParseError::InvalidNumber(full.to_string())) diff --git a/src/uu/df/src/blocks.rs b/src/uu/df/src/blocks.rs index fad8f7ac0c4..d7a689d8c86 100644 --- a/src/uu/df/src/blocks.rs +++ b/src/uu/df/src/blocks.rs @@ -9,7 +9,7 @@ use std::{env, fmt}; use uucore::{ display::Quotable, - parse_size::{parse_size, ParseSizeError}, + parse_size::{parse_size_u64, ParseSizeError}, }; /// The first ten powers of 1024. @@ -165,7 +165,7 @@ impl Default for BlockSize { pub(crate) fn read_block_size(matches: &ArgMatches) -> Result { if matches.contains_id(OPT_BLOCKSIZE) { let s = matches.get_one::(OPT_BLOCKSIZE).unwrap(); - let bytes = parse_size(s)?; + let bytes = parse_size_u64(s)?; if bytes > 0 { Ok(BlockSize::Bytes(bytes)) @@ -184,7 +184,7 @@ pub(crate) fn read_block_size(matches: &ArgMatches) -> Result Option { for env_var in ["DF_BLOCK_SIZE", "BLOCK_SIZE", "BLOCKSIZE"] { if let Ok(env_size) = env::var(env_var) { - if let Ok(size) = parse_size(&env_size) { + if let Ok(size) = parse_size_u64(&env_size) { return Some(size); } else { return None; diff --git a/src/uu/du/src/du.rs b/src/uu/du/src/du.rs index 5be2a8a2b34..ad5e87833ef 100644 --- a/src/uu/du/src/du.rs +++ b/src/uu/du/src/du.rs @@ -34,7 +34,7 @@ use uucore::error::FromIo; use uucore::error::{set_exit_code, UError, UResult}; use uucore::line_ending::LineEnding; use uucore::parse_glob; -use uucore::parse_size::{parse_size, ParseSizeError}; +use uucore::parse_size::{parse_size_u64, ParseSizeError}; use uucore::{ crash, format_usage, help_about, help_section, help_usage, show, show_error, show_warning, }; @@ -256,12 +256,12 @@ fn get_file_info(path: &Path) -> Option { fn read_block_size(s: Option<&str>) -> u64 { if let Some(s) = s { - parse_size(s) + parse_size_u64(s) .unwrap_or_else(|e| crash!(1, "{}", format_error_message(&e, s, options::BLOCK_SIZE))) } else { for env_var in ["DU_BLOCK_SIZE", "BLOCK_SIZE", "BLOCKSIZE"] { if let Ok(env_size) = env::var(env_var) { - if let Ok(v) = parse_size(&env_size) { + if let Ok(v) = parse_size_u64(&env_size) { return v; } } @@ -946,7 +946,7 @@ impl FromStr for Threshold { fn from_str(s: &str) -> std::result::Result { let offset = usize::from(s.starts_with(&['-', '+'][..])); - let size = parse_size(&s[offset..])?; + let size = parse_size_u64(&s[offset..])?; if s.starts_with('-') { // Threshold of '-0' excludes everything besides 0 sized entries diff --git a/src/uu/head/src/parse.rs b/src/uu/head/src/parse.rs index 062a1844ce8..dce60bae012 100644 --- a/src/uu/head/src/parse.rs +++ b/src/uu/head/src/parse.rs @@ -4,7 +4,7 @@ // file that was distributed with this source code. use std::ffi::OsString; -use uucore::parse_size::{parse_size, ParseSizeError}; +use uucore::parse_size::{parse_size_u64, ParseSizeError}; #[derive(PartialEq, Eq, Debug)] pub enum ParseError { @@ -129,7 +129,7 @@ pub fn parse_num(src: &str) -> Result<(u64, bool), ParseSizeError> { if trimmed_string.is_empty() { Ok((0, all_but_last)) } else { - parse_size(trimmed_string).map(|n| (n, all_but_last)) + parse_size_u64(trimmed_string).map(|n| (n, all_but_last)) } } diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index dfa637d2300..50fbe0c7c4a 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -57,7 +57,7 @@ use uucore::{ error::{set_exit_code, UError, UResult}, format_usage, fs::display_permissions, - parse_size::parse_size, + parse_size::parse_size_u64, version_cmp::version_cmp, }; use uucore::{help_about, help_section, help_usage, parse_glob, show, show_error, show_warning}; @@ -781,7 +781,7 @@ impl Config { }; let block_size: Option = if !opt_si && !opt_hr && !raw_bs.is_empty() { - match parse_size(&raw_bs.to_string_lossy()) { + match parse_size_u64(&raw_bs.to_string_lossy()) { Ok(size) => Some(size), Err(_) => { show!(LsError::BlockSizeParseError( diff --git a/src/uu/od/src/parse_nrofbytes.rs b/src/uu/od/src/parse_nrofbytes.rs index 431b2a71fde..1aa69909f2b 100644 --- a/src/uu/od/src/parse_nrofbytes.rs +++ b/src/uu/od/src/parse_nrofbytes.rs @@ -2,7 +2,7 @@ // // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -use uucore::parse_size::{parse_size, ParseSizeError}; +use uucore::parse_size::{parse_size_u64, ParseSizeError}; pub fn parse_number_of_bytes(s: &str) -> Result { let mut start = 0; @@ -15,7 +15,7 @@ pub fn parse_number_of_bytes(s: &str) -> Result { } else if s.starts_with('0') { radix = 8; } else { - return parse_size(&s[start..]); + return parse_size_u64(&s[start..]); } let mut ends_with = s.chars().rev(); diff --git a/src/uu/shred/src/shred.rs b/src/uu/shred/src/shred.rs index 9b1f7fc9831..eb63f0e5f2e 100644 --- a/src/uu/shred/src/shred.rs +++ b/src/uu/shred/src/shred.rs @@ -16,7 +16,7 @@ use std::os::unix::prelude::PermissionsExt; use std::path::{Path, PathBuf}; use uucore::display::Quotable; use uucore::error::{FromIo, UResult, USimpleError, UUsageError}; -use uucore::parse_size::parse_size; +use uucore::parse_size::parse_size_u64; use uucore::{format_usage, help_about, help_section, help_usage, show, show_error, show_if_err}; const ABOUT: &str = help_about!("shred.md"); @@ -319,7 +319,7 @@ pub fn uu_app() -> Command { fn get_size(size_str_opt: Option) -> Option { size_str_opt .as_ref() - .and_then(|size| parse_size(size.as_str()).ok()) + .and_then(|size| parse_size_u64(size.as_str()).ok()) .or_else(|| { if let Some(size) = size_str_opt { show_error!("invalid file size: {}", size.quote()); diff --git a/src/uu/split/src/split.rs b/src/uu/split/src/split.rs index 84b5900cc2c..cff8a4a4c57 100644 --- a/src/uu/split/src/split.rs +++ b/src/uu/split/src/split.rs @@ -22,7 +22,7 @@ use std::path::Path; use std::u64; use uucore::display::Quotable; use uucore::error::{FromIo, UIoError, UResult, USimpleError, UUsageError}; -use uucore::parse_size::{parse_size, parse_size_max, ParseSizeError}; +use uucore::parse_size::{parse_size_u64, parse_size_u64_max, ParseSizeError}; use uucore::uio_error; use uucore::{format_usage, help_about, help_section, help_usage}; @@ -503,7 +503,7 @@ impl NumberType { let parts: Vec<&str> = s.split('/').collect(); match &parts[..] { [n_str] => { - let num_chunks = parse_size(n_str) + let num_chunks = parse_size_u64(n_str) .map_err(|_| NumberTypeError::NumberOfChunks(n_str.to_string()))?; if num_chunks > 0 { Ok(Self::Bytes(num_chunks)) @@ -512,9 +512,9 @@ impl NumberType { } } [k_str, n_str] if !k_str.starts_with('l') && !k_str.starts_with('r') => { - let num_chunks = parse_size(n_str) + let num_chunks = parse_size_u64(n_str) .map_err(|_| NumberTypeError::NumberOfChunks(n_str.to_string()))?; - let chunk_number = parse_size(k_str) + let chunk_number = parse_size_u64(k_str) .map_err(|_| NumberTypeError::ChunkNumber(k_str.to_string()))?; if is_invalid_chunk(chunk_number, num_chunks) { return Err(NumberTypeError::ChunkNumber(k_str.to_string())); @@ -522,14 +522,14 @@ impl NumberType { Ok(Self::KthBytes(chunk_number, num_chunks)) } ["l", n_str] => { - let num_chunks = parse_size(n_str) + let num_chunks = parse_size_u64(n_str) .map_err(|_| NumberTypeError::NumberOfChunks(n_str.to_string()))?; Ok(Self::Lines(num_chunks)) } ["l", k_str, n_str] => { - let num_chunks = parse_size(n_str) + let num_chunks = parse_size_u64(n_str) .map_err(|_| NumberTypeError::NumberOfChunks(n_str.to_string()))?; - let chunk_number = parse_size(k_str) + let chunk_number = parse_size_u64(k_str) .map_err(|_| NumberTypeError::ChunkNumber(k_str.to_string()))?; if is_invalid_chunk(chunk_number, num_chunks) { return Err(NumberTypeError::ChunkNumber(k_str.to_string())); @@ -537,14 +537,14 @@ impl NumberType { Ok(Self::KthLines(chunk_number, num_chunks)) } ["r", n_str] => { - let num_chunks = parse_size(n_str) + let num_chunks = parse_size_u64(n_str) .map_err(|_| NumberTypeError::NumberOfChunks(n_str.to_string()))?; Ok(Self::RoundRobin(num_chunks)) } ["r", k_str, n_str] => { - let num_chunks = parse_size(n_str) + let num_chunks = parse_size_u64(n_str) .map_err(|_| NumberTypeError::NumberOfChunks(n_str.to_string()))?; - let chunk_number = parse_size(k_str) + let chunk_number = parse_size_u64(k_str) .map_err(|_| NumberTypeError::ChunkNumber(k_str.to_string()))?; if is_invalid_chunk(chunk_number, num_chunks) { return Err(NumberTypeError::ChunkNumber(k_str.to_string())); @@ -616,7 +616,7 @@ impl Strategy { error: fn(ParseSizeError) -> StrategyError, ) -> Result { let s = matches.get_one::(option).unwrap(); - let n = parse_size_max(s).map_err(error)?; + let n = parse_size_u64_max(s).map_err(error)?; if n > 0 { Ok(strategy(n)) } else { @@ -635,7 +635,7 @@ impl Strategy { matches.value_source(OPT_NUMBER) == Some(ValueSource::CommandLine), ) { (Some(v), false, false, false, false) => { - let v = parse_size_max(v).map_err(|_| { + let v = parse_size_u64_max(v).map_err(|_| { StrategyError::Lines(ParseSizeError::ParseFailure(v.to_string())) })?; if v > 0 { diff --git a/src/uu/stdbuf/src/stdbuf.rs b/src/uu/stdbuf/src/stdbuf.rs index 6e522aa3d7d..8578282752e 100644 --- a/src/uu/stdbuf/src/stdbuf.rs +++ b/src/uu/stdbuf/src/stdbuf.rs @@ -14,7 +14,7 @@ use std::process; use tempfile::tempdir; use tempfile::TempDir; use uucore::error::{FromIo, UResult, USimpleError, UUsageError}; -use uucore::parse_size::parse_size; +use uucore::parse_size::parse_size_u64; use uucore::{crash, format_usage, help_about, help_section, help_usage}; const ABOUT: &str = help_about!("stdbuf.md"); @@ -101,7 +101,7 @@ fn check_option(matches: &ArgMatches, name: &str) -> Result parse_size(x).map_or_else( + x => parse_size_u64(x).map_or_else( |e| crash!(125, "invalid mode {}", e), |m| { Ok(BufferType::Size(m.try_into().map_err(|_| { diff --git a/src/uu/tail/src/args.rs b/src/uu/tail/src/args.rs index 388842a1422..9b172987233 100644 --- a/src/uu/tail/src/args.rs +++ b/src/uu/tail/src/args.rs @@ -15,7 +15,7 @@ use std::ffi::OsString; use std::io::IsTerminal; use std::time::Duration; use uucore::error::{UResult, USimpleError, UUsageError}; -use uucore::parse_size::{parse_size, ParseSizeError}; +use uucore::parse_size::{parse_size_u64, ParseSizeError}; use uucore::{format_usage, help_about, help_usage, show_warning}; const ABOUT: &str = help_about!("tail.md"); @@ -414,7 +414,7 @@ fn parse_num(src: &str) -> Result { } } - match parse_size(size_string) { + match parse_size_u64(size_string) { Ok(n) => match (n, starting_with) { (0, true) => Ok(Signum::PlusZero), (0, false) => Ok(Signum::MinusZero), diff --git a/src/uu/truncate/src/truncate.rs b/src/uu/truncate/src/truncate.rs index 6e1c19fde9f..9368ce9b170 100644 --- a/src/uu/truncate/src/truncate.rs +++ b/src/uu/truncate/src/truncate.rs @@ -12,7 +12,7 @@ use std::os::unix::fs::FileTypeExt; use std::path::Path; use uucore::display::Quotable; use uucore::error::{FromIo, UResult, USimpleError, UUsageError}; -use uucore::parse_size::{parse_size, ParseSizeError}; +use uucore::parse_size::{parse_size_u64, ParseSizeError}; use uucore::{format_usage, help_about, help_section, help_usage}; #[derive(Debug, Eq, PartialEq)] @@ -380,7 +380,7 @@ fn is_modifier(c: char) -> bool { /// Parse a size string with optional modifier symbol as its first character. /// -/// A size string is as described in [`parse_size`]. The first character +/// A size string is as described in [`parse_size_u64`]. The first character /// of `size_string` might be a modifier symbol, like `'+'` or /// `'<'`. The first element of the pair returned by this function /// indicates which modifier symbol was present, or @@ -406,7 +406,7 @@ fn parse_mode_and_size(size_string: &str) -> Result TruncateMode::Extend, '-' => TruncateMode::Reduce, '<' => TruncateMode::AtMost, diff --git a/src/uucore/src/lib/parser/parse_size.rs b/src/uucore/src/lib/parser/parse_size.rs index 4d9968bb737..83917dd2586 100644 --- a/src/uucore/src/lib/parser/parse_size.rs +++ b/src/uucore/src/lib/parser/parse_size.rs @@ -51,7 +51,7 @@ impl<'parser> Parser<'parser> { /// Parse a size string into a number of bytes. /// /// A size string comprises an integer and an optional unit. The unit - /// may be K, M, G, T, P, E, Z or Y (powers of 1024), or KB, MB, + /// may be K, M, G, T, P, E, Z, Y, R or Q (powers of 1024), or KB, MB, /// etc. (powers of 1000), or b which is 512. /// Binary prefixes can be used, too: KiB=K, MiB=M, and so on. /// @@ -65,13 +65,18 @@ impl<'parser> Parser<'parser> { /// # Examples /// /// ```rust - /// use uucore::parse_size::parse_size; - /// assert_eq!(Ok(123), parse_size("123")); - /// assert_eq!(Ok(9 * 1000), parse_size("9kB")); // kB is 1000 - /// assert_eq!(Ok(2 * 1024), parse_size("2K")); // K is 1024 - /// assert_eq!(Ok(44251 * 1024), parse_size("0xACDBK")); + /// use uucore::parse_size::Parser; + /// let parser = Parser { + /// default_unit: Some("M"), + /// ..Default::default() + /// }; + /// assert_eq!(Ok(123 * 1024 * 1024), parser.parse("123M")); // M is 1024^2 + /// assert_eq!(Ok(123 * 1024 * 1024), parser.parse("123")); // default unit set to "M" on parser instance + /// assert_eq!(Ok(9 * 1000), parser.parse("9kB")); // kB is 1000 + /// assert_eq!(Ok(2 * 1024), parser.parse("2K")); // K is 1024 + /// assert_eq!(Ok(44251 * 1024), parser.parse("0xACDBK")); // 0xACDB is 44251 in decimal /// ``` - pub fn parse(&self, size: &str) -> Result { + pub fn parse(&self, size: &str) -> Result { if size.is_empty() { return Err(ParseSizeError::parse_failure(size)); } @@ -135,6 +140,8 @@ impl<'parser> Parser<'parser> { "EiB" | "eiB" | "E" | "e" => (1024, 6), "ZiB" | "ziB" | "Z" | "z" => (1024, 7), "YiB" | "yiB" | "Y" | "y" => (1024, 8), + "RiB" | "riB" | "R" | "r" => (1024, 9), + "QiB" | "qiB" | "Q" | "q" => (1024, 10), "KB" | "kB" => (1000, 1), "MB" | "mB" => (1000, 2), "GB" | "gB" => (1000, 3), @@ -143,16 +150,15 @@ impl<'parser> Parser<'parser> { "EB" | "eB" => (1000, 6), "ZB" | "zB" => (1000, 7), "YB" | "yB" => (1000, 8), + "RB" | "rB" => (1000, 9), + "QB" | "qB" => (1000, 10), _ if numeric_string.is_empty() => return Err(ParseSizeError::parse_failure(size)), _ => return Err(ParseSizeError::invalid_suffix(size)), }; - let factor = match u64::try_from(base.pow(exponent)) { - Ok(n) => n, - Err(_) => return Err(ParseSizeError::size_too_big(size)), - }; + let factor = base.pow(exponent); - // parse string into u64 - let number: u64 = match number_system { + // parse string into u128 + let number: u128 = match number_system { NumberSystem::Decimal => { if numeric_string.is_empty() { 1 @@ -175,6 +181,59 @@ impl<'parser> Parser<'parser> { .ok_or_else(|| ParseSizeError::size_too_big(size)) } + /// Explicit u128 alias for `parse()` + pub fn parse_u128(&self, size: &str) -> Result { + self.parse(size) + } + + /// Same as `parse()` but tries to return u64 + pub fn parse_u64(&self, size: &str) -> Result { + match self.parse(size) { + Ok(num_u128) => { + let num_u64 = match u64::try_from(num_u128) { + Ok(n) => n, + Err(_) => return Err(ParseSizeError::size_too_big(size)), + }; + Ok(num_u64) + } + Err(e) => Err(e), + } + } + + /// Same as `parse_u64()`, except returns `u64::MAX` on overflow + /// GNU lib/coreutils include similar functionality + /// and GNU test suite checks this behavior for some utils + pub fn parse_u64_max(&self, size: &str) -> Result { + let result = self.parse_u64(size); + match result { + Ok(_) => result, + Err(error) => { + if let ParseSizeError::SizeTooBig(_) = error { + Ok(u64::MAX) + } else { + Err(error) + } + } + } + } + + /// Same as `parse_u128()`, except returns `u128::MAX` on overflow + /// /// GNU lib/coreutils include similar functionality + /// and GNU test suite checks this behavior for some utils + pub fn parse_u128_max(&self, size: &str) -> Result { + let result = self.parse_u128(size); + match result { + Ok(_) => result, + Err(error) => { + if let ParseSizeError::SizeTooBig(_) = error { + Ok(u128::MAX) + } else { + Err(error) + } + } + } + } + fn determine_number_system(size: &str) -> NumberSystem { if size.len() <= 1 { return NumberSystem::Decimal; @@ -201,55 +260,50 @@ impl<'parser> Parser<'parser> { numeric_string: &str, radix: u32, original_size: &str, - ) -> Result { - u64::from_str_radix(numeric_string, radix).map_err(|e| match e.kind() { + ) -> Result { + u128::from_str_radix(numeric_string, radix).map_err(|e| match e.kind() { IntErrorKind::PosOverflow => ParseSizeError::size_too_big(original_size), _ => ParseSizeError::ParseFailure(original_size.to_string()), }) } } -/// Parse a size string into a number of bytes. -/// -/// A size string comprises an integer and an optional unit. The unit -/// may be K, M, G, T, P, E, Z or Y (powers of 1024), or KB, MB, -/// etc. (powers of 1000), or b which is 512. -/// Binary prefixes can be used, too: KiB=K, MiB=M, and so on. -/// -/// # Errors -/// -/// Will return `ParseSizeError` if it's not possible to parse this -/// string into a number, e.g. if the string does not begin with a -/// numeral, or if the unit is not one of the supported units described -/// in the preceding section. +/// Parse a size string into a number of bytes +/// using Default Parser (no custom settings) /// /// # Examples /// /// ```rust -/// use uucore::parse_size::parse_size; -/// assert_eq!(Ok(123), parse_size("123")); -/// assert_eq!(Ok(9 * 1000), parse_size("9kB")); // kB is 1000 -/// assert_eq!(Ok(2 * 1024), parse_size("2K")); // K is 1024 +/// use uucore::parse_size::parse_size_u128; +/// assert_eq!(Ok(123), parse_size_u128("123")); +/// assert_eq!(Ok(9 * 1000), parse_size_u128("9kB")); // kB is 1000 +/// assert_eq!(Ok(2 * 1024), parse_size_u128("2K")); // K is 1024 +/// assert_eq!(Ok(44251 * 1024), parse_size_u128("0xACDBK")); /// ``` -pub fn parse_size(size: &str) -> Result { +pub fn parse_size_u128(size: &str) -> Result { Parser::default().parse(size) } -/// Same as `parse_size()`, except returns `u64::MAX` on overflow +/// Same as `parse_size_u128()`, but for u64 +pub fn parse_size_u64(size: &str) -> Result { + Parser::default().parse_u64(size) +} + +#[deprecated = "Please use parse_size_u64(size: &str) -> Result OR parse_size_u128(size: &str) -> Result instead."] +pub fn parse_size(size: &str) -> Result { + parse_size_u64(size) +} + +/// Same as `parse_size_u64()`, except returns `u64::MAX` on overflow /// GNU lib/coreutils include similar functionality /// and GNU test suite checks this behavior for some utils -pub fn parse_size_max(size: &str) -> Result { - let result = Parser::default().parse(size); - match result { - Ok(_) => result, - Err(error) => { - if let ParseSizeError::SizeTooBig(_) = error { - Ok(u64::MAX) - } else { - Err(error) - } - } - } +pub fn parse_size_u64_max(size: &str) -> Result { + Parser::default().parse_u64_max(size) +} + +/// Same as `parse_size_u128()`, except returns `u128::MAX` on overflow +pub fn parse_size_u128_max(size: &str) -> Result { + Parser::default().parse_u128_max(size) } #[derive(Debug, PartialEq, Eq)] @@ -355,7 +409,7 @@ mod tests { #[test] fn all_suffixes() { - // Units are K,M,G,T,P,E,Z,Y (powers of 1024) or KB,MB,... (powers of 1000). + // Units are K,M,G,T,P,E,Z,Y,R,Q (powers of 1024) or KB,MB,... (powers of 1000). // Binary prefixes can be used, too: KiB=K, MiB=M, and so on. let suffixes = [ ('K', 1u32), @@ -364,60 +418,73 @@ mod tests { ('T', 4u32), ('P', 5u32), ('E', 6u32), - // The following will always result ParseSizeError::SizeTooBig as they cannot fit in u64 - // ('Z', 7u32), - // ('Y', 8u32), + ('Z', 7u32), + ('Y', 8u32), + ('R', 9u32), + ('Q', 10u32), ]; for &(c, exp) in &suffixes { let s = format!("2{c}B"); // KB - assert_eq!(Ok((2 * (1000_u128).pow(exp)) as u64), parse_size(&s)); + assert_eq!(Ok((2 * (1000_u128).pow(exp)) as u128), parse_size_u128(&s)); let s = format!("2{c}"); // K - assert_eq!(Ok((2 * (1024_u128).pow(exp)) as u64), parse_size(&s)); + assert_eq!(Ok((2 * (1024_u128).pow(exp)) as u128), parse_size_u128(&s)); let s = format!("2{c}iB"); // KiB - assert_eq!(Ok((2 * (1024_u128).pow(exp)) as u64), parse_size(&s)); + assert_eq!(Ok((2 * (1024_u128).pow(exp)) as u128), parse_size_u128(&s)); let s = format!("2{}iB", c.to_lowercase()); // kiB - assert_eq!(Ok((2 * (1024_u128).pow(exp)) as u64), parse_size(&s)); + assert_eq!(Ok((2 * (1024_u128).pow(exp)) as u128), parse_size_u128(&s)); // suffix only let s = format!("{c}B"); // KB - assert_eq!(Ok(((1000_u128).pow(exp)) as u64), parse_size(&s)); + assert_eq!(Ok(((1000_u128).pow(exp)) as u128), parse_size_u128(&s)); let s = format!("{c}"); // K - assert_eq!(Ok(((1024_u128).pow(exp)) as u64), parse_size(&s)); + assert_eq!(Ok(((1024_u128).pow(exp)) as u128), parse_size_u128(&s)); let s = format!("{c}iB"); // KiB - assert_eq!(Ok(((1024_u128).pow(exp)) as u64), parse_size(&s)); + assert_eq!(Ok(((1024_u128).pow(exp)) as u128), parse_size_u128(&s)); let s = format!("{}iB", c.to_lowercase()); // kiB - assert_eq!(Ok(((1024_u128).pow(exp)) as u64), parse_size(&s)); + assert_eq!(Ok(((1024_u128).pow(exp)) as u128), parse_size_u128(&s)); } } #[test] #[cfg(not(target_pointer_width = "128"))] fn overflow_x64() { - assert!(parse_size("10000000000000000000000").is_err()); - assert!(parse_size("1000000000T").is_err()); - assert!(parse_size("100000P").is_err()); - assert!(parse_size("100E").is_err()); - assert!(parse_size("1Z").is_err()); - assert!(parse_size("1Y").is_err()); + assert!(parse_size_u64("10000000000000000000000").is_err()); + assert!(parse_size_u64("1000000000T").is_err()); + assert!(parse_size_u64("100000P").is_err()); + assert!(parse_size_u64("100E").is_err()); + assert!(parse_size_u64("1Z").is_err()); + assert!(parse_size_u64("1Y").is_err()); + assert!(parse_size_u64("1R").is_err()); + assert!(parse_size_u64("1Q").is_err()); assert!(variant_eq( - &parse_size("1Z").unwrap_err(), + &parse_size_u64("1Z").unwrap_err(), &ParseSizeError::SizeTooBig(String::new()) )); assert_eq!( ParseSizeError::SizeTooBig("'1Y': Value too large for defined data type".to_string()), - parse_size("1Y").unwrap_err() + parse_size_u64("1Y").unwrap_err() + ); + assert_eq!( + ParseSizeError::SizeTooBig("'1R': Value too large for defined data type".to_string()), + parse_size_u64("1R").unwrap_err() + ); + assert_eq!( + ParseSizeError::SizeTooBig("'1Q': Value too large for defined data type".to_string()), + parse_size_u64("1Q").unwrap_err() ); } #[test] #[cfg(not(target_pointer_width = "128"))] fn overflow_to_max_x64() { - assert_eq!(Ok(u64::MAX), parse_size_max("18446744073709551616")); - assert_eq!(Ok(u64::MAX), parse_size_max("10000000000000000000000")); - assert_eq!(Ok(u64::MAX), parse_size_max("1Y")); + assert_eq!(Ok(u64::MAX), parse_size_u64_max("18446744073709551616")); + assert_eq!(Ok(u64::MAX), parse_size_u64_max("10000000000000000000000")); + assert_eq!(Ok(u64::MAX), parse_size_u64_max("1Y")); + assert_eq!(Ok(u64::MAX), parse_size_u64_max("1R")); + assert_eq!(Ok(u64::MAX), parse_size_u64_max("1Q")); } #[test] @@ -425,7 +492,7 @@ mod tests { let test_strings = ["5mib", "1eb", "1H"]; for &test_string in &test_strings { assert_eq!( - parse_size(test_string).unwrap_err(), + parse_size_u64(test_string).unwrap_err(), ParseSizeError::InvalidSuffix(format!("{}", test_string.quote())) ); } @@ -436,7 +503,7 @@ mod tests { let test_strings = ["biB", "-", "+", "", "-1", "∞"]; for &test_string in &test_strings { assert_eq!( - parse_size(test_string).unwrap_err(), + parse_size_u64(test_string).unwrap_err(), ParseSizeError::ParseFailure(format!("{}", test_string.quote())) ); } @@ -444,57 +511,83 @@ mod tests { #[test] fn b_suffix() { - assert_eq!(Ok(3 * 512), parse_size("3b")); // b is 512 + assert_eq!(Ok(3 * 512), parse_size_u64("3b")); // b is 512 } #[test] fn no_suffix() { - assert_eq!(Ok(1234), parse_size("1234")); - assert_eq!(Ok(0), parse_size("0")); - assert_eq!(Ok(5), parse_size("5")); - assert_eq!(Ok(999), parse_size("999")); + assert_eq!(Ok(1234), parse_size_u64("1234")); + assert_eq!(Ok(0), parse_size_u64("0")); + assert_eq!(Ok(5), parse_size_u64("5")); + assert_eq!(Ok(999), parse_size_u64("999")); } #[test] fn kilobytes_suffix() { - assert_eq!(Ok(123 * 1000), parse_size("123KB")); // KB is 1000 - assert_eq!(Ok(9 * 1000), parse_size("9kB")); // kB is 1000 - assert_eq!(Ok(2 * 1024), parse_size("2K")); // K is 1024 - assert_eq!(Ok(0), parse_size("0K")); - assert_eq!(Ok(0), parse_size("0KB")); - assert_eq!(Ok(1000), parse_size("KB")); - assert_eq!(Ok(1024), parse_size("K")); - assert_eq!(Ok(2000), parse_size("2kB")); - assert_eq!(Ok(4000), parse_size("4KB")); + assert_eq!(Ok(123 * 1000), parse_size_u64("123KB")); // KB is 1000 + assert_eq!(Ok(9 * 1000), parse_size_u64("9kB")); // kB is 1000 + assert_eq!(Ok(2 * 1024), parse_size_u64("2K")); // K is 1024 + assert_eq!(Ok(0), parse_size_u64("0K")); + assert_eq!(Ok(0), parse_size_u64("0KB")); + assert_eq!(Ok(1000), parse_size_u64("KB")); + assert_eq!(Ok(1024), parse_size_u64("K")); + assert_eq!(Ok(2000), parse_size_u64("2kB")); + assert_eq!(Ok(4000), parse_size_u64("4KB")); } #[test] fn megabytes_suffix() { - assert_eq!(Ok(123 * 1024 * 1024), parse_size("123M")); - assert_eq!(Ok(123 * 1000 * 1000), parse_size("123MB")); - assert_eq!(Ok(1024 * 1024), parse_size("M")); - assert_eq!(Ok(1000 * 1000), parse_size("MB")); - assert_eq!(Ok(2 * 1_048_576), parse_size("2m")); - assert_eq!(Ok(4 * 1_048_576), parse_size("4M")); - assert_eq!(Ok(2_000_000), parse_size("2mB")); - assert_eq!(Ok(4_000_000), parse_size("4MB")); + assert_eq!(Ok(123 * 1024 * 1024), parse_size_u64("123M")); + assert_eq!(Ok(123 * 1000 * 1000), parse_size_u64("123MB")); + assert_eq!(Ok(1024 * 1024), parse_size_u64("M")); + assert_eq!(Ok(1000 * 1000), parse_size_u64("MB")); + assert_eq!(Ok(2 * 1_048_576), parse_size_u64("2m")); + assert_eq!(Ok(4 * 1_048_576), parse_size_u64("4M")); + assert_eq!(Ok(2_000_000), parse_size_u64("2mB")); + assert_eq!(Ok(4_000_000), parse_size_u64("4MB")); } #[test] fn gigabytes_suffix() { - assert_eq!(Ok(1_073_741_824), parse_size("1G")); - assert_eq!(Ok(2_000_000_000), parse_size("2GB")); + assert_eq!(Ok(1_073_741_824), parse_size_u64("1G")); + assert_eq!(Ok(2_000_000_000), parse_size_u64("2GB")); } #[test] #[cfg(target_pointer_width = "64")] fn x64() { - assert_eq!(Ok(1_099_511_627_776), parse_size("1T")); - assert_eq!(Ok(1_125_899_906_842_624), parse_size("1P")); - assert_eq!(Ok(1_152_921_504_606_846_976), parse_size("1E")); - assert_eq!(Ok(2_000_000_000_000), parse_size("2TB")); - assert_eq!(Ok(2_000_000_000_000_000), parse_size("2PB")); - assert_eq!(Ok(2_000_000_000_000_000_000), parse_size("2EB")); + assert_eq!(Ok(1_099_511_627_776), parse_size_u64("1T")); + assert_eq!(Ok(1_125_899_906_842_624), parse_size_u64("1P")); + assert_eq!(Ok(1_152_921_504_606_846_976), parse_size_u64("1E")); + + assert_eq!(Ok(1_180_591_620_717_411_303_424), parse_size_u128("1Z")); + assert_eq!(Ok(1_208_925_819_614_629_174_706_176), parse_size_u128("1Y")); + assert_eq!( + Ok(1_237_940_039_285_380_274_899_124_224), + parse_size_u128("1R") + ); + assert_eq!( + Ok(1_267_650_600_228_229_401_496_703_205_376), + parse_size_u128("1Q") + ); + + assert_eq!(Ok(2_000_000_000_000), parse_size_u64("2TB")); + assert_eq!(Ok(2_000_000_000_000_000), parse_size_u64("2PB")); + assert_eq!(Ok(2_000_000_000_000_000_000), parse_size_u64("2EB")); + + assert_eq!(Ok(2_000_000_000_000_000_000_000), parse_size_u128("2ZB")); + assert_eq!( + Ok(2_000_000_000_000_000_000_000_000), + parse_size_u128("2YB") + ); + assert_eq!( + Ok(2_000_000_000_000_000_000_000_000_000), + parse_size_u128("2RB") + ); + assert_eq!( + Ok(2_000_000_000_000_000_000_000_000_000_000), + parse_size_u128("2QB") + ); } #[test] @@ -539,15 +632,15 @@ mod tests { #[test] fn parse_octal_size() { - assert_eq!(Ok(63), parse_size("077")); - assert_eq!(Ok(528), parse_size("01020")); - assert_eq!(Ok(668 * 1024), parse_size("01234K")); + assert_eq!(Ok(63), parse_size_u64("077")); + assert_eq!(Ok(528), parse_size_u64("01020")); + assert_eq!(Ok(668 * 1024), parse_size_u128("01234K")); } #[test] fn parse_hex_size() { - assert_eq!(Ok(10), parse_size("0xA")); - assert_eq!(Ok(94722), parse_size("0x17202")); - assert_eq!(Ok(44251 * 1024), parse_size("0xACDBK")); + assert_eq!(Ok(10), parse_size_u64("0xA")); + assert_eq!(Ok(94722), parse_size_u64("0x17202")); + assert_eq!(Ok(44251 * 1024), parse_size_u128("0xACDBK")); } } diff --git a/tests/by-util/test_head.rs b/tests/by-util/test_head.rs index 65aeca4377c..f536b26ae08 100644 --- a/tests/by-util/test_head.rs +++ b/tests/by-util/test_head.rs @@ -297,11 +297,15 @@ fn test_head_invalid_num() { new_ucmd!() .args(&["-c", "1024R", "emptyfile.txt"]) .fails() - .stderr_is("head: invalid number of bytes: '1024R'\n"); + .stderr_is( + "head: invalid number of bytes: '1024R': Value too large for defined data type\n", + ); new_ucmd!() .args(&["-n", "1024R", "emptyfile.txt"]) .fails() - .stderr_is("head: invalid number of lines: '1024R'\n"); + .stderr_is( + "head: invalid number of lines: '1024R': Value too large for defined data type\n", + ); #[cfg(not(target_pointer_width = "128"))] new_ucmd!() .args(&["-c", "1Y", "emptyfile.txt"]) diff --git a/tests/by-util/test_split.rs b/tests/by-util/test_split.rs index 113c0fb87ff..be3933df38d 100644 --- a/tests/by-util/test_split.rs +++ b/tests/by-util/test_split.rs @@ -642,10 +642,10 @@ fn test_split_obs_lines_within_combined_with_number() { #[test] fn test_split_invalid_bytes_size() { new_ucmd!() - .args(&["-b", "1024R"]) + .args(&["-b", "1024W"]) .fails() .code_is(1) - .stderr_only("split: invalid number of bytes: '1024R'\n"); + .stderr_only("split: invalid number of bytes: '1024W'\n"); #[cfg(target_pointer_width = "32")] { let sizes = ["1000G", "10T"]; diff --git a/tests/by-util/test_truncate.rs b/tests/by-util/test_truncate.rs index 972b4fc5bfd..81b87ed2e3c 100644 --- a/tests/by-util/test_truncate.rs +++ b/tests/by-util/test_truncate.rs @@ -248,7 +248,7 @@ fn test_truncate_bytes_size() { .args(&["--size", "1024R", "file"]) .fails() .code_is(1) - .stderr_only("truncate: Invalid number: '1024R'\n"); + .stderr_only("truncate: Invalid number: '1024R': Value too large for defined data type\n"); #[cfg(not(target_pointer_width = "128"))] new_ucmd!() .args(&["--size", "1Y", "file"]) From 7038657a447bd96b7b323be58e7d386af6a6db0d Mon Sep 17 00:00:00 2001 From: zhitkoff Date: Wed, 4 Oct 2023 21:57:33 -0400 Subject: [PATCH 0242/2851] implement R and Q prefixes --- tests/by-util/test_stdbuf.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/by-util/test_stdbuf.rs b/tests/by-util/test_stdbuf.rs index e31c532e22a..9a67dad9e37 100644 --- a/tests/by-util/test_stdbuf.rs +++ b/tests/by-util/test_stdbuf.rs @@ -65,7 +65,7 @@ fn test_stdbuf_invalid_mode_fails() { .args(&[*option, "1024R", "head"]) .fails() .code_is(125) - .stderr_only("stdbuf: invalid mode '1024R'\n"); + .stderr_only("stdbuf: invalid mode '1024R': Value too large for defined data type\n"); #[cfg(not(target_pointer_width = "128"))] new_ucmd!() .args(&[*option, "1Y", "head"]) From 74e01e39877bf171b3a1eba725e913eab92dc1a8 Mon Sep 17 00:00:00 2001 From: zhitkoff Date: Thu, 5 Oct 2023 16:11:11 -0400 Subject: [PATCH 0243/2851] parse_size: more test case coverage --- src/uucore/src/lib/parser/parse_size.rs | 29 +++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/src/uucore/src/lib/parser/parse_size.rs b/src/uucore/src/lib/parser/parse_size.rs index 83917dd2586..337ad396b01 100644 --- a/src/uucore/src/lib/parser/parse_size.rs +++ b/src/uucore/src/lib/parser/parse_size.rs @@ -479,7 +479,9 @@ mod tests { #[test] #[cfg(not(target_pointer_width = "128"))] - fn overflow_to_max_x64() { + fn overflow_to_max_u64() { + assert_eq!(Ok(1_099_511_627_776), parse_size_u64_max("1T")); + assert_eq!(Ok(1_125_899_906_842_624), parse_size_u64_max("1P")); assert_eq!(Ok(u64::MAX), parse_size_u64_max("18446744073709551616")); assert_eq!(Ok(u64::MAX), parse_size_u64_max("10000000000000000000000")); assert_eq!(Ok(u64::MAX), parse_size_u64_max("1Y")); @@ -487,6 +489,21 @@ mod tests { assert_eq!(Ok(u64::MAX), parse_size_u64_max("1Q")); } + #[test] + #[cfg(not(target_pointer_width = "128"))] + fn overflow_to_max_u128() { + assert_eq!( + Ok(12_379_400_392_853_802_748_991_242_240), + parse_size_u128_max("10R") + ); + assert_eq!( + Ok(12_676_506_002_282_294_014_967_032_053_760), + parse_size_u128_max("10Q") + ); + assert_eq!(Ok(u128::MAX), parse_size_u128_max("1000000000000R")); + assert_eq!(Ok(u128::MAX), parse_size_u128_max("1000000000Q")); + } + #[test] fn invalid_suffix() { let test_strings = ["5mib", "1eb", "1H"]; @@ -610,7 +627,7 @@ mod tests { parser .with_allow_list(&[ - "b", "k", "K", "m", "M", "MB", "g", "G", "t", "T", "P", "E", "Z", "Y", + "b", "k", "K", "m", "M", "MB", "g", "G", "t", "T", "P", "E", "Z", "Y", "R", "Q", ]) .with_default_unit("K") .with_b_byte_count(true); @@ -620,6 +637,14 @@ mod tests { assert_eq!(Ok(1000 * 1000), parser.parse("1MB")); assert_eq!(Ok(1024 * 1024), parser.parse("1M")); assert_eq!(Ok(1024 * 1024 * 1024), parser.parse("1G")); + assert_eq!( + Ok(1_237_940_039_285_380_274_899_124_224), + parser.parse_u128("1R") + ); + assert_eq!( + Ok(1_267_650_600_228_229_401_496_703_205_376), + parser.parse_u128("1Q") + ); assert_eq!(Ok(1), parser.parse("1b")); assert_eq!(Ok(1024), parser.parse("1024b")); From abc95361a5d3043cca41118e201f4bbbbdf5ce18 Mon Sep 17 00:00:00 2001 From: zhitkoff Date: Thu, 5 Oct 2023 16:23:24 -0400 Subject: [PATCH 0244/2851] prase_size: comments --- src/uucore/src/lib/parser/parse_size.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/uucore/src/lib/parser/parse_size.rs b/src/uucore/src/lib/parser/parse_size.rs index 337ad396b01..0a46ce3271b 100644 --- a/src/uucore/src/lib/parser/parse_size.rs +++ b/src/uucore/src/lib/parser/parse_size.rs @@ -202,7 +202,7 @@ impl<'parser> Parser<'parser> { /// Same as `parse_u64()`, except returns `u64::MAX` on overflow /// GNU lib/coreutils include similar functionality - /// and GNU test suite checks this behavior for some utils + /// and GNU test suite checks this behavior for some utils (`split` for example) pub fn parse_u64_max(&self, size: &str) -> Result { let result = self.parse_u64(size); match result { @@ -217,9 +217,7 @@ impl<'parser> Parser<'parser> { } } - /// Same as `parse_u128()`, except returns `u128::MAX` on overflow - /// /// GNU lib/coreutils include similar functionality - /// and GNU test suite checks this behavior for some utils + /// Same as `parse_u64_max()`, except for u128, i.e. returns `u128::MAX` on overflow pub fn parse_u128_max(&self, size: &str) -> Result { let result = self.parse_u128(size); match result { From b6a6a4dc6506db94ec7dc4702314c78bff3be0ab Mon Sep 17 00:00:00 2001 From: terade <134976752+terade@users.noreply.github.com.> Date: Tue, 17 Oct 2023 17:26:27 +0200 Subject: [PATCH 0245/2851] rm: apply suggestion of retrieving metatada in function --- src/uu/rm/src/rm.rs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/uu/rm/src/rm.rs b/src/uu/rm/src/rm.rs index 0f4d045985d..43a7ec774e6 100644 --- a/src/uu/rm/src/rm.rs +++ b/src/uu/rm/src/rm.rs @@ -514,22 +514,18 @@ fn prompt_file(path: &Path, options: &Options) -> bool { prompt_yes!("remove file {}?", path.quote()) }; } - prompt_file_permission_readonly(path, Ok(metadata)) } Err(err) => { if err.kind() != ErrorKind::PermissionDenied { return true; } - prompt_file_permission_readonly(path, fs::metadata(path)) } } + prompt_file_permission_readonly(path) } -fn prompt_file_permission_readonly( - path: &Path, - metadata_or_err: Result, -) -> bool { - match metadata_or_err { +fn prompt_file_permission_readonly(path: &Path) -> bool { + match fs::metadata(path) { Ok(metadata) if !metadata.permissions().readonly() => true, Ok(metadata) if metadata.len() == 0 => prompt_yes!( "remove write-protected regular empty file {}?", From 12765a445e4bde82616d4c36af1fd9a7509b2ac0 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Wed, 18 Oct 2023 10:11:13 +0200 Subject: [PATCH 0246/2851] fuzz: set LC_COLLATE=C for expr --- fuzz/fuzz_targets/fuzz_expr.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/fuzz/fuzz_targets/fuzz_expr.rs b/fuzz/fuzz_targets/fuzz_expr.rs index fb7b1730956..c2217c48abc 100644 --- a/fuzz/fuzz_targets/fuzz_expr.rs +++ b/fuzz/fuzz_targets/fuzz_expr.rs @@ -10,7 +10,7 @@ use uu_expr::uumain; use rand::seq::SliceRandom; use rand::Rng; -use std::ffi::OsString; +use std::{env, ffi::OsString}; mod fuzz_common; use crate::fuzz_common::{generate_and_run_uumain, run_gnu_cmd}; @@ -86,6 +86,11 @@ fuzz_target!(|_data: &[u8]| { let (rust_output, uumain_exit_code) = generate_and_run_uumain(&args, uumain); + // Use C locale to avoid false positives, like in https://github.com/uutils/coreutils/issues/5378, + // because uutils expr doesn't support localization yet + // TODO remove once uutils expr supports localization + env::set_var("LC_COLLATE", "C"); + // Run GNU expr with the provided arguments and compare the output match run_gnu_cmd(CMD_PATH, &args[1..], true) { Ok((gnu_output, gnu_exit_code)) => { From a2defd1a21f66b2e4e5a6462349424f7d995ff3c Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Wed, 18 Oct 2023 16:34:35 +0200 Subject: [PATCH 0247/2851] uucore: remove incorrect comment --- src/uucore/src/lib/features/fs.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uucore/src/lib/features/fs.rs b/src/uucore/src/lib/features/fs.rs index 97238b10df4..84ed006d975 100644 --- a/src/uucore/src/lib/features/fs.rs +++ b/src/uucore/src/lib/features/fs.rs @@ -435,6 +435,7 @@ pub fn canonicalize>( } #[cfg(not(unix))] +/// Display the permissions of a file pub fn display_permissions(metadata: &fs::Metadata, display_file_type: bool) -> String { let write = if metadata.permissions().readonly() { '-' @@ -459,7 +460,6 @@ pub fn display_permissions(metadata: &fs::Metadata, display_file_type: bool) -> #[cfg(unix)] /// Display the permissions of a file -/// On non unix like system, just show '----------' pub fn display_permissions(metadata: &fs::Metadata, display_file_type: bool) -> String { let mode: mode_t = metadata.mode() as mode_t; display_permissions_unix(mode, display_file_type) From 1ab8555a94ec128e7f8f9dd172c96e7d375ebce4 Mon Sep 17 00:00:00 2001 From: Zhuoxun Yang Date: Wed, 18 Oct 2023 22:50:43 +0800 Subject: [PATCH 0248/2851] pathchk: check empty path --- src/uu/pathchk/src/pathchk.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/uu/pathchk/src/pathchk.rs b/src/uu/pathchk/src/pathchk.rs index 3510a3327b3..81c35208864 100644 --- a/src/uu/pathchk/src/pathchk.rs +++ b/src/uu/pathchk/src/pathchk.rs @@ -193,6 +193,17 @@ fn check_default(path: &[String]) -> bool { ); return false; } + if total_len == 0 { + // Check whether a file name component is in a directory that is not searchable, + // or has some other serious problem. POSIX does not allow "" as a file name, + // but some non-POSIX hosts do (as an alias for "."), + // so allow "" if `symlink_metadata` (corresponds to `lstat`) does. + if fs::symlink_metadata(&joined_path).is_err() { + writeln!(std::io::stderr(), "pathchk: '': No such file or directory",); + return false; + } + } + // components: length for p in path { let component_len = p.len(); From f63f9a06f62340ded772fac93e732af80e534208 Mon Sep 17 00:00:00 2001 From: Zhuoxun Yang Date: Wed, 18 Oct 2023 22:50:54 +0800 Subject: [PATCH 0249/2851] tests/pathchk: test empty path --- tests/by-util/test_pathchk.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/by-util/test_pathchk.rs b/tests/by-util/test_pathchk.rs index f497cfe898d..e1f4bc539b7 100644 --- a/tests/by-util/test_pathchk.rs +++ b/tests/by-util/test_pathchk.rs @@ -19,8 +19,10 @@ fn test_default_mode() { // accept non-portable chars new_ucmd!().args(&["dir#/$file"]).succeeds().no_stdout(); - // accept empty path - new_ucmd!().args(&[""]).succeeds().no_stdout(); + // fail on empty path + new_ucmd!().args(&[""]).fails().no_stdout(); + + new_ucmd!().args(&["", ""]).fails().no_stdout(); // fail on long path new_ucmd!() From 44240915921214c1027a0520487a8af85d3ba67e Mon Sep 17 00:00:00 2001 From: Zhuoxun Yang Date: Thu, 19 Oct 2023 01:29:53 +0800 Subject: [PATCH 0250/2851] tests/pathchk: check error message --- tests/by-util/test_pathchk.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/tests/by-util/test_pathchk.rs b/tests/by-util/test_pathchk.rs index e1f4bc539b7..d66ecb9efb4 100644 --- a/tests/by-util/test_pathchk.rs +++ b/tests/by-util/test_pathchk.rs @@ -20,9 +20,15 @@ fn test_default_mode() { new_ucmd!().args(&["dir#/$file"]).succeeds().no_stdout(); // fail on empty path - new_ucmd!().args(&[""]).fails().no_stdout(); + new_ucmd!() + .args(&[""]) + .fails() + .stderr_only("pathchk: '': No such file or directory\n"); - new_ucmd!().args(&["", ""]).fails().no_stdout(); + new_ucmd!().args(&["", ""]).fails().stderr_only( + "pathchk: '': No such file or directory\n\ + pathchk: '': No such file or directory\n", + ); // fail on long path new_ucmd!() From 90c2dbb0ccc532d1ba014525d9df02caee26b3bb Mon Sep 17 00:00:00 2001 From: David Matos Date: Thu, 19 Oct 2023 09:50:21 +0200 Subject: [PATCH 0251/2851] mv: make UpdateMode public --- src/uu/mv/src/mv.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/uu/mv/src/mv.rs b/src/uu/mv/src/mv.rs index 9888389aefe..02c856ce075 100644 --- a/src/uu/mv/src/mv.rs +++ b/src/uu/mv/src/mv.rs @@ -19,12 +19,12 @@ use std::os::unix; #[cfg(windows)] use std::os::windows; use std::path::{Path, PathBuf}; -pub use uucore::backup_control::BackupMode; use uucore::backup_control::{self, source_is_target_backup}; use uucore::display::Quotable; use uucore::error::{set_exit_code, FromIo, UError, UResult, USimpleError, UUsageError}; use uucore::fs::{are_hardlinks_or_one_way_symlink_to_same_file, are_hardlinks_to_same_file}; -use uucore::update_control::{self, UpdateMode}; +use uucore::update_control; +pub use uucore::{backup_control::BackupMode, update_control::UpdateMode}; use uucore::{format_usage, help_about, help_section, help_usage, prompt_yes, show}; use fs_extra::dir::{ From 383dcde51a7ad501dddbf22dc36f2296532fed90 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Thu, 19 Oct 2023 10:08:45 +0200 Subject: [PATCH 0252/2851] mv: comment why some imports are public --- src/uu/mv/src/mv.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/uu/mv/src/mv.rs b/src/uu/mv/src/mv.rs index 02c856ce075..7236907da77 100644 --- a/src/uu/mv/src/mv.rs +++ b/src/uu/mv/src/mv.rs @@ -24,6 +24,8 @@ use uucore::display::Quotable; use uucore::error::{set_exit_code, FromIo, UError, UResult, USimpleError, UUsageError}; use uucore::fs::{are_hardlinks_or_one_way_symlink_to_same_file, are_hardlinks_to_same_file}; use uucore::update_control; +// These are exposed for projects (e.g. nushell) that want to create an `Options` value, which +// requires these enums pub use uucore::{backup_control::BackupMode, update_control::UpdateMode}; use uucore::{format_usage, help_about, help_section, help_usage, prompt_yes, show}; From f971a69d69c57eed1fe31807cd069cfbdc923463 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Thu, 19 Oct 2023 14:17:34 +0200 Subject: [PATCH 0253/2851] ls --dired -R: fix the positions (#5341) * move get_offset_from_previous_line into a specific function * dired: improve the -R support * dired: fix the display with subdir * ls --dired -R: fix the positions * ls --dired -R: verify also the SUBDIRED coordinate * ls --dired -R: add a long file name and fix a windows test * dired: always put dired first in the args + minor fixes Co-authored-by: Daniel Hofstetter * ls: add cognitive_complexity to silent a warning --------- Co-authored-by: Daniel Hofstetter --- src/uu/ls/src/dired.rs | 204 +++++++++++++++++++++++++++++++++------ src/uu/ls/src/ls.rs | 41 ++++++-- tests/by-util/test_ls.rs | 102 ++++++++++++++++++++ 3 files changed, 312 insertions(+), 35 deletions(-) diff --git a/src/uu/ls/src/dired.rs b/src/uu/ls/src/dired.rs index 8574e750e2d..74732d37a9f 100644 --- a/src/uu/ls/src/dired.rs +++ b/src/uu/ls/src/dired.rs @@ -9,14 +9,14 @@ use std::fmt; use std::io::{BufWriter, Stdout, Write}; use uucore::error::UResult; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub struct BytePosition { pub start: usize, pub end: usize, } /// Represents the output structure for DIRED, containing positions for both DIRED and SUBDIRED. -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone, Default, PartialEq)] pub struct DiredOutput { pub dired_positions: Vec, pub subdired_positions: Vec, @@ -32,17 +32,21 @@ impl fmt::Display for BytePosition { // When --dired is used, all lines starts with 2 spaces static DIRED_TRAILING_OFFSET: usize = 2; +fn get_offset_from_previous_line(dired_positions: &[BytePosition]) -> usize { + if let Some(last_position) = dired_positions.last() { + last_position.end + 1 + } else { + 0 + } +} + /// Calculates the byte positions for DIRED pub fn calculate_dired( + dired_positions: &[BytePosition], output_display_len: usize, dfn_len: usize, - dired_positions: &[BytePosition], ) -> (usize, usize) { - let offset_from_previous_line = if let Some(last_position) = dired_positions.last() { - last_position.end + 1 - } else { - 0 - }; + let offset_from_previous_line = get_offset_from_previous_line(dired_positions); let start = output_display_len + offset_from_previous_line; let end = start + dfn_len; @@ -55,15 +59,18 @@ pub fn indent(out: &mut BufWriter) -> UResult<()> { } pub fn calculate_subdired(dired: &mut DiredOutput, path_len: usize) { - let offset = if dired.subdired_positions.is_empty() { - DIRED_TRAILING_OFFSET + let offset_from_previous_line = get_offset_from_previous_line(&dired.dired_positions); + + let additional_offset = if dired.subdired_positions.is_empty() { + 0 } else { - dired.subdired_positions[dired.subdired_positions.len() - 1].start + DIRED_TRAILING_OFFSET + // if we have several directories: \n\n + 2 }; - dired.subdired_positions.push(BytePosition { - start: offset, - end: path_len + offset, - }); + + let start = offset_from_previous_line + DIRED_TRAILING_OFFSET + additional_offset; + let end = start + path_len; + dired.subdired_positions.push(BytePosition { start, end }); } /// Prints the dired output based on the given configuration and dired structure. @@ -73,10 +80,11 @@ pub fn print_dired_output( out: &mut BufWriter, ) -> UResult<()> { out.flush()?; + if dired.padding == 0 && !dired.dired_positions.is_empty() { + print_positions("//DIRED//", &dired.dired_positions); + } if config.recursive { print_positions("//SUBDIRED//", &dired.subdired_positions); - } else if dired.padding == 0 { - print_positions("//DIRED//", &dired.dired_positions); } println!("//DIRED-OPTIONS// --quoting-style={}", config.quoting_style); Ok(()) @@ -91,17 +99,31 @@ fn print_positions(prefix: &str, positions: &Vec) { println!(); } -pub fn add_total(total_len: usize, dired: &mut DiredOutput) { - // when dealing with " total: xx", it isn't part of the //DIRED// - // so, we just keep the size line to add it to the position of the next file - dired.padding = total_len + DIRED_TRAILING_OFFSET; +pub fn add_total(dired: &mut DiredOutput, total_len: usize) { + if dired.padding == 0 { + let offset_from_previous_line = get_offset_from_previous_line(&dired.dired_positions); + // when dealing with " total: xx", it isn't part of the //DIRED// + // so, we just keep the size line to add it to the position of the next file + dired.padding = total_len + offset_from_previous_line + DIRED_TRAILING_OFFSET; + } else { + // += because if we are in -R, we have " dir:\n total X". So, we need to take the + // previous padding too. + // and we already have the previous position in mind + dired.padding += total_len + DIRED_TRAILING_OFFSET; + } +} + +// when using -R, we have the dirname. we need to add it to the padding +pub fn add_dir_name(dired: &mut DiredOutput, dir_len: usize) { + // 1 for the ":" in " dirname:" + dired.padding += dir_len + DIRED_TRAILING_OFFSET + 1; } /// Calculates byte positions and updates the dired structure. pub fn calculate_and_update_positions( + dired: &mut DiredOutput, output_display_len: usize, dfn_len: usize, - dired: &mut DiredOutput, ) { let offset = dired .dired_positions @@ -111,14 +133,14 @@ pub fn calculate_and_update_positions( }); let start = output_display_len + offset + DIRED_TRAILING_OFFSET; let end = start + dfn_len; - update_positions(start, end, dired); + update_positions(dired, start, end); } /// Updates the dired positions based on the given start and end positions. /// update when it is the first element in the list (to manage "total X") /// insert when it isn't the about total -pub fn update_positions(start: usize, end: usize, dired: &mut DiredOutput) { - // padding can be 0 but as it doesn't matter< +pub fn update_positions(dired: &mut DiredOutput, start: usize, end: usize) { + // padding can be 0 but as it doesn't matter dired.dired_positions.push(BytePosition { start: start + dired.padding, end: end + dired.padding, @@ -136,12 +158,112 @@ mod tests { let output_display = "sample_output".to_string(); let dfn = "sample_file".to_string(); let dired_positions = vec![BytePosition { start: 5, end: 10 }]; - let (start, end) = calculate_dired(output_display.len(), dfn.len(), &dired_positions); + let (start, end) = calculate_dired(&dired_positions, output_display.len(), dfn.len()); assert_eq!(start, 24); assert_eq!(end, 35); } + #[test] + fn test_get_offset_from_previous_line() { + let positions = vec![ + BytePosition { start: 0, end: 3 }, + BytePosition { start: 4, end: 7 }, + BytePosition { start: 8, end: 11 }, + ]; + assert_eq!(get_offset_from_previous_line(&positions), 12); + } + #[test] + fn test_calculate_subdired() { + let mut dired = DiredOutput { + dired_positions: vec![ + BytePosition { start: 0, end: 3 }, + BytePosition { start: 4, end: 7 }, + BytePosition { start: 8, end: 11 }, + ], + subdired_positions: vec![], + padding: 0, + }; + let path_len = 5; + calculate_subdired(&mut dired, path_len); + assert_eq!( + dired.subdired_positions, + vec![BytePosition { start: 14, end: 19 }], + ); + } + + #[test] + fn test_add_dir_name() { + let mut dired = DiredOutput { + dired_positions: vec![ + BytePosition { start: 0, end: 3 }, + BytePosition { start: 4, end: 7 }, + BytePosition { start: 8, end: 11 }, + ], + subdired_positions: vec![], + padding: 0, + }; + let dir_len = 5; + add_dir_name(&mut dired, dir_len); + assert_eq!( + dired, + DiredOutput { + dired_positions: vec![ + BytePosition { start: 0, end: 3 }, + BytePosition { start: 4, end: 7 }, + BytePosition { start: 8, end: 11 }, + ], + subdired_positions: vec![], + // 8 = 1 for the \n + 5 for dir_len + 2 for " " + 1 for : + padding: 8 + } + ); + } + + #[test] + fn test_add_total() { + let mut dired = DiredOutput { + dired_positions: vec![ + BytePosition { start: 0, end: 3 }, + BytePosition { start: 4, end: 7 }, + BytePosition { start: 8, end: 11 }, + ], + subdired_positions: vec![], + padding: 0, + }; + // if we have "total: 2" + let total_len = 8; + add_total(&mut dired, total_len); + // 22 = 8 (len) + 2 (padding) + 11 (previous position) + 1 (\n) + assert_eq!(dired.padding, 22); + } + + #[test] + fn test_add_dir_name_and_total() { + // test when we have + // dirname: + // total 0 + // -rw-r--r-- 1 sylvestre sylvestre 0 Sep 30 09:41 ab + + let mut dired = DiredOutput { + dired_positions: vec![ + BytePosition { start: 0, end: 3 }, + BytePosition { start: 4, end: 7 }, + BytePosition { start: 8, end: 11 }, + ], + subdired_positions: vec![], + padding: 0, + }; + let dir_len = 5; + add_dir_name(&mut dired, dir_len); + // 8 = 2 (" ") + 1 (\n) + 5 + 1 (: of dirname) + assert_eq!(dired.padding, 8); + + let total_len = 8; + add_total(&mut dired, total_len); + assert_eq!(dired.padding, 18); + } + #[test] fn test_dired_update_positions() { let mut dired = DiredOutput { @@ -151,15 +273,41 @@ mod tests { }; // Test with adjust = true - update_positions(15, 20, &mut dired); + update_positions(&mut dired, 15, 20); let last_position = dired.dired_positions.last().unwrap(); assert_eq!(last_position.start, 25); // 15 + 10 (end of the previous position) assert_eq!(last_position.end, 30); // 20 + 10 (end of the previous position) // Test with adjust = false - update_positions(30, 35, &mut dired); + update_positions(&mut dired, 30, 35); let last_position = dired.dired_positions.last().unwrap(); assert_eq!(last_position.start, 30); assert_eq!(last_position.end, 35); } + + #[test] + fn test_calculate_and_update_positions() { + let mut dired = DiredOutput { + dired_positions: vec![ + BytePosition { start: 0, end: 3 }, + BytePosition { start: 4, end: 7 }, + BytePosition { start: 8, end: 11 }, + ], + subdired_positions: vec![], + padding: 5, + }; + let output_display_len = 15; + let dfn_len = 5; + calculate_and_update_positions(&mut dired, output_display_len, dfn_len); + assert_eq!( + dired.dired_positions, + vec![ + BytePosition { start: 0, end: 3 }, + BytePosition { start: 4, end: 7 }, + BytePosition { start: 8, end: 11 }, + BytePosition { start: 32, end: 37 }, + ] + ); + assert_eq!(dired.padding, 0); + } } diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 50fbe0c7c4a..41d2f59b131 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -1856,6 +1856,10 @@ impl PathData { } } +fn show_dir_name(dir: &Path, out: &mut BufWriter) { + write!(out, "{}:", dir.display()).unwrap(); +} + #[allow(clippy::cognitive_complexity)] pub fn list(locs: Vec<&Path>, config: &Config) -> UResult<()> { let mut files = Vec::::new(); @@ -1922,10 +1926,17 @@ pub fn list(locs: Vec<&Path>, config: &Config) -> UResult<()> { } writeln!(out, "{}:", path_data.p_buf.display())?; if config.dired { - dired::calculate_subdired(&mut dired, path_data.display_name.len()); + // First directory displayed + let dir_len = path_data.display_name.len(); + // add the //SUBDIRED// coordinates + dired::calculate_subdired(&mut dired, dir_len); + // Add the padding for the dir name + dired::add_dir_name(&mut dired, dir_len); } } else { - writeln!(out, "\n{}:", path_data.p_buf.display())?; + writeln!(out)?; + show_dir_name(&path_data.p_buf, &mut out); + writeln!(out)?; } } let mut listed_ancestors = HashSet::new(); @@ -2104,7 +2115,7 @@ fn enter_directory( let total = return_total(&entries, config, out)?; write!(out, "{}", total.as_str())?; if config.dired { - dired::add_total(total.len(), dired); + dired::add_total(dired, total.len()); } } @@ -2132,7 +2143,23 @@ fn enter_directory( if listed_ancestors .insert(FileInformation::from_path(&e.p_buf, e.must_dereference)?) { - writeln!(out, "\n{}:", e.p_buf.display())?; + // when listing several directories in recursive mode, we show + // "dirname:" at the beginning of the file list + writeln!(out)?; + if config.dired { + // We already injected the first dir + // Continue with the others + // 2 = \n + \n + dired.padding = 2; + dired::indent(out)?; + let dir_name_size = e.p_buf.to_string_lossy().len(); + dired::calculate_subdired(dired, dir_name_size); + // inject dir name + dired::add_dir_name(dired, dir_name_size); + } + + show_dir_name(&e.p_buf, out); + writeln!(out)?; enter_directory(e, rd, config, out, listed_ancestors, dired)?; listed_ancestors .remove(&FileInformation::from_path(&e.p_buf, e.must_dereference)?); @@ -2547,11 +2574,11 @@ fn display_item_long( let displayed_file = display_file_name(item, config, None, String::new(), out).contents; if config.dired { let (start, end) = dired::calculate_dired( + &dired.dired_positions, output_display.len(), displayed_file.len(), - &dired.dired_positions, ); - dired::update_positions(start, end, dired); + dired::update_positions(dired, start, end); } write!(output_display, "{}{}", displayed_file, config.line_ending).unwrap(); } else { @@ -2639,9 +2666,9 @@ fn display_item_long( if config.dired { dired::calculate_and_update_positions( + dired, output_display.len(), displayed_file.trim().len(), - dired, ); } write!(output_display, "{}{}", displayed_file, config.line_ending).unwrap(); diff --git a/tests/by-util/test_ls.rs b/tests/by-util/test_ls.rs index 8b0032065d1..15893b0e2ba 100644 --- a/tests/by-util/test_ls.rs +++ b/tests/by-util/test_ls.rs @@ -3580,6 +3580,57 @@ fn test_ls_dired_recursive() { .stdout_contains("//DIRED-OPTIONS// --quoting-style"); } +#[test] +fn test_ls_dired_recursive_multiple() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + at.mkdir("d"); + at.mkdir("d/d1"); + at.mkdir("d/d2"); + at.touch("d/d2/a"); + at.touch("d/d2/c2"); + at.touch("d/d1/f1"); + at.touch("d/d1/file-long"); + + let mut cmd = scene.ucmd(); + cmd.arg("--dired").arg("-l").arg("-R").arg("d"); + + let result = cmd.succeeds(); + + let output = result.stdout_str().to_string(); + println!("Output:\n{}", output); + + let dired_line = output + .lines() + .find(|&line| line.starts_with("//DIRED//")) + .unwrap(); + let positions: Vec = dired_line + .split_whitespace() + .skip(1) + .map(|s| s.parse().unwrap()) + .collect(); + println!("Parsed byte positions: {:?}", positions); + assert_eq!(positions.len() % 2, 0); // Ensure there's an even number of positions + + let filenames: Vec = positions + .chunks(2) + .map(|chunk| { + let start_pos = chunk[0]; + let end_pos = chunk[1]; + let filename = String::from_utf8(output.as_bytes()[start_pos..=end_pos].to_vec()) + .unwrap() + .trim() + .to_string(); + println!("Extracted filename: {}", filename); + filename + }) + .collect(); + + println!("Extracted filenames: {:?}", filenames); + assert_eq!(filenames, vec!["d1", "d2", "f1", "file-long", "a", "c2"]); +} + #[test] fn test_ls_dired_simple() { let scene = TestScenario::new(util_name!()); @@ -3679,6 +3730,57 @@ fn test_ls_dired_complex() { assert_eq!(filenames, vec!["a1", "a22", "a333", "a4444", "d"]); } +#[test] +fn test_ls_subdired_complex() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + at.mkdir("dir1"); + at.mkdir("dir1/d"); + at.mkdir("dir1/c2"); + at.touch("dir1/a1"); + at.touch("dir1/a22"); + at.touch("dir1/a333"); + at.touch("dir1/c2/a4444"); + + let mut cmd = scene.ucmd(); + cmd.arg("--dired").arg("-l").arg("-R").arg("dir1"); + let result = cmd.succeeds(); + + let output = result.stdout_str().to_string(); + println!("Output:\n{}", output); + + let dired_line = output + .lines() + .find(|&line| line.starts_with("//SUBDIRED//")) + .unwrap(); + let positions: Vec = dired_line + .split_whitespace() + .skip(1) + .map(|s| s.parse().unwrap()) + .collect(); + println!("Parsed byte positions: {:?}", positions); + assert_eq!(positions.len() % 2, 0); // Ensure there's an even number of positions + + let dirnames: Vec = positions + .chunks(2) + .map(|chunk| { + let start_pos = chunk[0]; + let end_pos = chunk[1]; + let dirname = + String::from_utf8(output.as_bytes()[start_pos..end_pos].to_vec()).unwrap(); + println!("Extracted dirname: {}", dirname); + dirname + }) + .collect(); + + println!("Extracted dirnames: {:?}", dirnames); + #[cfg(unix)] + assert_eq!(dirnames, vec!["dir1", "dir1/c2", "dir1/d"]); + #[cfg(windows)] + assert_eq!(dirnames, vec!["dir1", "dir1\\c2", "dir1\\d"]); +} + #[ignore = "issue #5396"] #[test] fn test_ls_cf_output_should_be_delimited_by_tab() { From 1562ef52e8d1edec352230dc0b3930b9c2222e8a Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Thu, 19 Oct 2023 07:15:05 +0200 Subject: [PATCH 0254/2851] Bump rustix crates 0.36.15 -> 0.36.16 0.37.23 -> 0.37.26 0.38.8 -> 0.38.20 --- Cargo.lock | 26 +++++++++++++------------- deny.toml | 4 ++-- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index dfa22ad2747..6c6926db9ea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1223,9 +1223,9 @@ checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" [[package]] name = "linux-raw-sys" -version = "0.4.5" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503" +checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f" [[package]] name = "lock_api" @@ -1614,7 +1614,7 @@ dependencies = [ "byteorder", "hex", "lazy_static", - "rustix 0.36.15", + "rustix 0.36.16", ] [[package]] @@ -1832,9 +1832,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.36.15" +version = "0.36.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c37f1bd5ef1b5422177b7646cba67430579cfe2ace80f284fee876bca52ad941" +checksum = "6da3636faa25820d8648e0e31c5d519bbb01f72fdf57131f0f5f7da5fed36eab" dependencies = [ "bitflags 1.3.2", "errno", @@ -1846,9 +1846,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.37.23" +version = "0.37.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d69718bf81c6127a49dc64e44a742e8bb9213c0ff8869a22c308f84c1d4ab06" +checksum = "84f3f8f960ed3b5a59055428714943298bf3fa2d4a1d53135084e0544829d995" dependencies = [ "bitflags 1.3.2", "errno", @@ -1860,14 +1860,14 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.8" +version = "0.38.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19ed4fa021d81c8392ce04db050a3da9a60299050b7ae1cf482d862b54a7218f" +checksum = "67ce50cb2e16c2903e30d1cbccfd8387a74b9d4c938b6a4c5ec6cc7556f7a8a0" dependencies = [ "bitflags 2.4.0", "errno", "libc", - "linux-raw-sys 0.4.5", + "linux-raw-sys 0.4.10", "windows-sys 0.48.0", ] @@ -2081,7 +2081,7 @@ dependencies = [ "cfg-if", "fastrand", "redox_syscall 0.3.5", - "rustix 0.38.8", + "rustix 0.38.20", "windows-sys 0.48.0", ] @@ -2091,7 +2091,7 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e6bf6f19e9f8ed8d4048dc22981458ebcf406d67e94cd422e5ecd73d63b3237" dependencies = [ - "rustix 0.37.23", + "rustix 0.37.26", "windows-sys 0.48.0", ] @@ -2101,7 +2101,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7" dependencies = [ - "rustix 0.38.8", + "rustix 0.38.20", "windows-sys 0.48.0", ] diff --git a/deny.toml b/deny.toml index b1707d035cb..fa8f77c0128 100644 --- a/deny.toml +++ b/deny.toml @@ -59,12 +59,12 @@ highlight = "all" # spell-checker: disable skip = [ # procfs - { name = "rustix", version = "0.36.15" }, + { name = "rustix", version = "0.36.16" }, # rustix { name = "linux-raw-sys", version = "0.1.4" }, { name = "linux-raw-sys", version = "0.3.8" }, # terminal_size - { name = "rustix", version = "0.37.23" }, + { name = "rustix", version = "0.37.26" }, # various crates { name = "windows-sys", version = "0.45.0" }, # windows-sys From eede467e21d54eeaa0f8147375a4f83d68b1b40d Mon Sep 17 00:00:00 2001 From: Yury Zhytkou <54360928+zhitkoff@users.noreply.github.com> Date: Fri, 20 Oct 2023 02:47:32 -0400 Subject: [PATCH 0255/2851] `split` : `--filter` and stdin updates (#5418) --- src/uu/split/src/split.rs | 229 +++++++++++++++++++++++++++--------- tests/by-util/test_split.rs | 48 ++++++++ 2 files changed, 220 insertions(+), 57 deletions(-) diff --git a/src/uu/split/src/split.rs b/src/uu/split/src/split.rs index cff8a4a4c57..71b6f34dc2d 100644 --- a/src/uu/split/src/split.rs +++ b/src/uu/split/src/split.rs @@ -748,6 +748,12 @@ enum SettingsError { /// Multiple different separator characters MultipleSeparatorCharacters, + /// Using `--filter` with `--number` option sub-strategies that print Kth chunk out of N chunks to stdout + /// K/N + /// l/K/N + /// r/K/N + FilterWithKthChunkNumber, + /// The `--filter` option is not supported on Windows. #[cfg(windows)] NotSupported, @@ -780,6 +786,9 @@ impl fmt::Display for SettingsError { "invalid suffix {}, contains directory separator", s.quote() ), + Self::FilterWithKthChunkNumber => { + write!(f, "--filter does not process a chunk extracted to stdout") + } #[cfg(windows)] Self::NotSupported => write!( f, @@ -850,12 +859,26 @@ impl Settings { filter: matches.get_one::(OPT_FILTER).map(|s| s.to_owned()), elide_empty_files: matches.get_flag(OPT_ELIDE_EMPTY_FILES), }; + #[cfg(windows)] if result.filter.is_some() { // see https://github.com/rust-lang/rust/issues/29494 return Err(SettingsError::NotSupported); } + // Return an error if `--filter` option is used with any of the + // Kth chunk sub-strategies of `--number` option + // As those are writing to stdout of `split` and cannot write to filter command child process + let kth_chunk = matches!( + result.strategy, + Strategy::Number(NumberType::KthBytes(_, _)) + | Strategy::Number(NumberType::KthLines(_, _)) + | Strategy::Number(NumberType::KthRoundRobin(_, _)) + ); + if kth_chunk && result.filter.is_some() { + return Err(SettingsError::FilterWithKthChunkNumber); + } + Ok(result) } @@ -871,6 +894,47 @@ impl Settings { } } +/// When using `--filter` option, writing to child command process stdin +/// could fail with BrokenPipe error +/// It can be safely ignored +fn ignorable_io_error(error: &std::io::Error, settings: &Settings) -> bool { + error.kind() == ErrorKind::BrokenPipe && settings.filter.is_some() +} + +/// Custom wrapper for `write()` method +/// Follows similar approach to GNU implementation +/// If ignorable io error occurs, return number of bytes as if all bytes written +/// Should not be used for Kth chunk number sub-strategies +/// as those do not work with `--filter` option +fn custom_write( + bytes: &[u8], + writer: &mut T, + settings: &Settings, +) -> std::io::Result { + match writer.write(bytes) { + Ok(n) => Ok(n), + Err(e) if ignorable_io_error(&e, settings) => Ok(bytes.len()), + Err(e) => Err(e), + } +} + +/// Custom wrapper for `write_all()` method +/// Similar to [`custom_write`], but returns true or false +/// depending on if `--filter` stdin is still open (no BrokenPipe error) +/// Should not be used for Kth chunk number sub-strategies +/// as those do not work with `--filter` option +fn custom_write_all( + bytes: &[u8], + writer: &mut T, + settings: &Settings, +) -> std::io::Result { + match writer.write_all(bytes) { + Ok(()) => Ok(true), + Err(e) if ignorable_io_error(&e, settings) => Ok(false), + Err(e) => Err(e), + } +} + /// Write a certain number of bytes to one file, then move on to another one. /// /// This struct maintains an underlying writer representing the @@ -935,6 +999,7 @@ impl<'a> ByteChunkWriter<'a> { } impl<'a> Write for ByteChunkWriter<'a> { + /// Implements `--bytes=SIZE` fn write(&mut self, mut buf: &[u8]) -> std::io::Result { // If the length of `buf` exceeds the number of bytes remaining // in the current chunk, we will need to write to multiple @@ -966,9 +1031,9 @@ impl<'a> Write for ByteChunkWriter<'a> { // bytes in `buf`, then write all the bytes in `buf`. Otherwise, // write enough bytes to fill the current chunk, then increment // the chunk number and repeat. - let n = buf.len(); - if (n as u64) < self.num_bytes_remaining_in_current_chunk { - let num_bytes_written = self.inner.write(buf)?; + let buf_len = buf.len(); + if (buf_len as u64) < self.num_bytes_remaining_in_current_chunk { + let num_bytes_written = custom_write(buf, &mut self.inner, self.settings)?; self.num_bytes_remaining_in_current_chunk -= num_bytes_written as u64; return Ok(carryover_bytes_written + num_bytes_written); } else { @@ -978,7 +1043,7 @@ impl<'a> Write for ByteChunkWriter<'a> { // self.num_bytes_remaining_in_current_chunk is lower than // n, which is already usize. let i = self.num_bytes_remaining_in_current_chunk as usize; - let num_bytes_written = self.inner.write(&buf[..i])?; + let num_bytes_written = custom_write(&buf[..i], &mut self.inner, self.settings)?; self.num_bytes_remaining_in_current_chunk -= num_bytes_written as u64; // It's possible that the underlying writer did not @@ -1064,6 +1129,7 @@ impl<'a> LineChunkWriter<'a> { } impl<'a> Write for LineChunkWriter<'a> { + /// Implements `--lines=NUMBER` fn write(&mut self, buf: &[u8]) -> std::io::Result { // If the number of lines in `buf` exceeds the number of lines // remaining in the current chunk, we will need to write to @@ -1092,14 +1158,16 @@ impl<'a> Write for LineChunkWriter<'a> { // Write the line, starting from *after* the previous // separator character and ending *after* the current // separator character. - let n = self.inner.write(&buf[prev..i + 1])?; - total_bytes_written += n; + let num_bytes_written = + custom_write(&buf[prev..i + 1], &mut self.inner, self.settings)?; + total_bytes_written += num_bytes_written; prev = i + 1; self.num_lines_remaining_in_current_chunk -= 1; } - let n = self.inner.write(&buf[prev..buf.len()])?; - total_bytes_written += n; + let num_bytes_written = + custom_write(&buf[prev..buf.len()], &mut self.inner, self.settings)?; + total_bytes_written += num_bytes_written; Ok(total_bytes_written) } @@ -1196,6 +1264,8 @@ impl<'a> Write for LineBytesChunkWriter<'a> { /// |------| |-------| |--------| |---| /// aaaaaaaa a\nbbbb\n cccc\ndd\n ee\n /// ``` + /// + /// Implements `--line-bytes=SIZE` fn write(&mut self, mut buf: &[u8]) -> std::io::Result { // The total number of bytes written during the loop below. // @@ -1248,7 +1318,11 @@ impl<'a> Write for LineBytesChunkWriter<'a> { { self.num_bytes_remaining_in_current_chunk = 0; } else { - let num_bytes_written = self.inner.write(&buf[..end.min(buf.len())])?; + let num_bytes_written = custom_write( + &buf[..end.min(buf.len())], + &mut self.inner, + self.settings, + )?; self.num_bytes_remaining_in_current_chunk -= num_bytes_written; total_bytes_written += num_bytes_written; buf = &buf[num_bytes_written..]; @@ -1261,7 +1335,8 @@ impl<'a> Write for LineBytesChunkWriter<'a> { // continue to the next iteration. (See chunk 1 in the // example comment above.) Some(i) if i < self.num_bytes_remaining_in_current_chunk => { - let num_bytes_written = self.inner.write(&buf[..i + 1])?; + let num_bytes_written = + custom_write(&buf[..i + 1], &mut self.inner, self.settings)?; self.num_bytes_remaining_in_current_chunk -= num_bytes_written; total_bytes_written += num_bytes_written; buf = &buf[num_bytes_written..]; @@ -1279,7 +1354,8 @@ impl<'a> Write for LineBytesChunkWriter<'a> { == self.chunk_size.try_into().unwrap() => { let end = self.num_bytes_remaining_in_current_chunk; - let num_bytes_written = self.inner.write(&buf[..end])?; + let num_bytes_written = + custom_write(&buf[..end], &mut self.inner, self.settings)?; self.num_bytes_remaining_in_current_chunk -= num_bytes_written; total_bytes_written += num_bytes_written; buf = &buf[num_bytes_written..]; @@ -1315,6 +1391,10 @@ impl<'a> Write for LineBytesChunkWriter<'a> { /// /// This function returns an error if there is a problem reading from /// `reader` or writing to one of the output files. +/// +/// Implements `--number=CHUNKS` +/// Where CHUNKS +/// * N fn split_into_n_chunks_by_byte( settings: &Settings, reader: &mut R, @@ -1378,29 +1458,26 @@ where writers.push(writer); } - // Capture the result of the `std::io::copy()` calls to check for - // `BrokenPipe`. - let result: std::io::Result<()> = { - // Write `chunk_size` bytes from the reader into each writer - // except the last. - // - // The last writer gets all remaining bytes so that if the number - // of bytes in the input file was not evenly divisible by - // `num_chunks`, we don't leave any bytes behind. - for writer in writers.iter_mut().take(num_chunks - 1) { - io::copy(&mut reader.by_ref().take(chunk_size), writer)?; - } - - // Write all the remaining bytes to the last chunk. - let i = num_chunks - 1; - let last_chunk_size = num_bytes - (chunk_size * (num_chunks as u64 - 1)); - io::copy(&mut reader.by_ref().take(last_chunk_size), &mut writers[i])?; + // Write `chunk_size` bytes from the reader into each writer + // except the last. + // + // The last writer gets all remaining bytes so that if the number + // of bytes in the input file was not evenly divisible by + // `num_chunks`, we don't leave any bytes behind. + for writer in writers.iter_mut().take(num_chunks - 1) { + match io::copy(&mut reader.by_ref().take(chunk_size), writer) { + Ok(_) => continue, + Err(e) if ignorable_io_error(&e, settings) => continue, + Err(e) => return Err(uio_error!(e, "input/output error")), + }; + } - Ok(()) - }; - match result { + // Write all the remaining bytes to the last chunk. + let i = num_chunks - 1; + let last_chunk_size = num_bytes - (chunk_size * (num_chunks as u64 - 1)); + match io::copy(&mut reader.by_ref().take(last_chunk_size), &mut writers[i]) { Ok(_) => Ok(()), - Err(e) if e.kind() == ErrorKind::BrokenPipe => Ok(()), + Err(e) if ignorable_io_error(&e, settings) => Ok(()), Err(e) => Err(uio_error!(e, "input/output error")), } } @@ -1415,6 +1492,10 @@ where /// /// This function returns an error if there is a problem reading from /// `reader` or writing to stdout. +/// +/// Implements `--number=CHUNKS` +/// Where CHUNKS +/// * K/N fn kth_chunks_by_byte( settings: &Settings, reader: &mut R, @@ -1508,6 +1589,10 @@ where /// /// * [`kth_chunk_by_line`], which splits its input in the same way, /// but writes only one specified chunk to stdout. +/// +/// Implements `--number=CHUNKS` +/// Where CHUNKS +/// * l/N fn split_into_n_chunks_by_line( settings: &Settings, reader: &mut R, @@ -1518,7 +1603,9 @@ where { // Get the size of the input file in bytes and compute the number // of bytes per chunk. - let metadata = metadata(&settings.input).unwrap(); + let metadata = metadata(&settings.input).map_err(|_| { + USimpleError::new(1, format!("{}: cannot determine file size", settings.input)) + })?; let num_bytes = metadata.len(); let chunk_size = (num_bytes / num_chunks) as usize; @@ -1550,8 +1637,8 @@ where let maybe_writer = writers.get_mut(i); let writer = maybe_writer.unwrap(); let bytes = line.as_slice(); - writer.write_all(bytes)?; - writer.write_all(&[sep])?; + custom_write_all(bytes, writer, settings)?; + custom_write_all(&[sep], writer, settings)?; // Add one byte for the separator character. let num_bytes = bytes.len() + 1; @@ -1581,6 +1668,10 @@ where /// /// * [`split_into_n_chunks_by_line`], which splits its input in the /// same way, but writes each chunk to its own file. +/// +/// Implements `--number=CHUNKS` +/// Where CHUNKS +/// * l/K/N fn kth_chunk_by_line( settings: &Settings, reader: &mut R, @@ -1592,7 +1683,9 @@ where { // Get the size of the input file in bytes and compute the number // of bytes per chunk. - let metadata = metadata(&settings.input).unwrap(); + let metadata = metadata(&settings.input).map_err(|_| { + USimpleError::new(1, format!("{}: cannot determine file size", settings.input)) + })?; let num_bytes = metadata.len(); let chunk_size = (num_bytes / num_chunks) as usize; @@ -1601,7 +1694,7 @@ where let mut writer = stdout.lock(); let mut num_bytes_remaining_in_current_chunk = chunk_size; - let mut i = 0; + let mut i = 1; let sep = settings.separator; for line_result in reader.split(sep) { let line = line_result?; @@ -1628,11 +1721,32 @@ where Ok(()) } +/// Split a file into a specific number of chunks by line, but +/// assign lines via round-robin +/// +/// This function always creates one output file for each chunk, even +/// if there is an error reading or writing one of the chunks or if +/// the input file is truncated. However, if the `filter` option is +/// being used, then no files are created. +/// +/// # Errors +/// +/// This function returns an error if there is a problem reading from +/// `reader` or writing to one of the output files. +/// +/// # See also +/// +/// * [`split_into_n_chunks_by_line`], which splits its input in the same way, +/// but without round robin distribution. +/// +/// Implements `--number=CHUNKS` +/// Where CHUNKS +/// * r/N fn split_into_n_chunks_by_line_round_robin( settings: &Settings, reader: &mut R, num_chunks: u64, -) -> std::io::Result<()> +) -> UResult<()> where R: BufRead, { @@ -1659,13 +1773,21 @@ where let num_chunks: usize = num_chunks.try_into().unwrap(); let sep = settings.separator; + let mut closed_writers = 0; for (i, line_result) in reader.split(sep).enumerate() { - let line = line_result.unwrap(); let maybe_writer = writers.get_mut(i % num_chunks); let writer = maybe_writer.unwrap(); + let mut line = line_result.unwrap(); + line.push(sep); let bytes = line.as_slice(); - writer.write_all(bytes)?; - writer.write_all(&[sep])?; + let writer_stdin_open = custom_write_all(bytes, writer, settings)?; + if !writer_stdin_open { + closed_writers += 1; + if closed_writers == num_chunks { + // all writers are closed - stop reading + break; + } + } } Ok(()) @@ -1689,6 +1811,10 @@ where /// /// * [`split_into_n_chunks_by_line_round_robin`], which splits its input in the /// same way, but writes each chunk to its own file. +/// +/// Implements `--number=CHUNKS` +/// Where CHUNKS +/// * r/K/N fn kth_chunk_by_line_round_robin( settings: &Settings, reader: &mut R, @@ -1705,6 +1831,10 @@ where let num_chunks: usize = num_chunks.try_into().unwrap(); let chunk_number: usize = chunk_number.try_into().unwrap(); let sep = settings.separator; + // The chunk number is given as a 1-indexed number, but it + // is a little easier to deal with a 0-indexed number + // since `.enumerate()` returns index `i` starting with 0 + let chunk_number = chunk_number - 1; for (i, line_result) in reader.split(sep).enumerate() { let line = line_result?; let bytes = line.as_slice(); @@ -1741,24 +1871,12 @@ fn split(settings: &Settings) -> UResult<()> { split_into_n_chunks_by_line(settings, &mut reader, num_chunks) } Strategy::Number(NumberType::KthLines(chunk_number, num_chunks)) => { - // The chunk number is given as a 1-indexed number, but it - // is a little easier to deal with a 0-indexed number. - let chunk_number = chunk_number - 1; kth_chunk_by_line(settings, &mut reader, chunk_number, num_chunks) } Strategy::Number(NumberType::RoundRobin(num_chunks)) => { - match split_into_n_chunks_by_line_round_robin(settings, &mut reader, num_chunks) { - Ok(_) => Ok(()), - Err(e) => match e.kind() { - ErrorKind::BrokenPipe => Ok(()), - _ => Err(USimpleError::new(1, format!("{e}"))), - }, - } + split_into_n_chunks_by_line_round_robin(settings, &mut reader, num_chunks) } Strategy::Number(NumberType::KthRoundRobin(chunk_number, num_chunks)) => { - // The chunk number is given as a 1-indexed number, but it - // is a little easier to deal with a 0-indexed number. - let chunk_number = chunk_number - 1; kth_chunk_by_line_round_robin(settings, &mut reader, chunk_number, num_chunks) } Strategy::Lines(chunk_size) => { @@ -1775,7 +1893,6 @@ fn split(settings: &Settings) -> UResult<()> { // indicate that. A special error message needs to be // printed in that case. ErrorKind::Other => Err(USimpleError::new(1, format!("{e}"))), - ErrorKind::BrokenPipe => Ok(()), _ => Err(uio_error!(e, "input/output error")), }, } @@ -1794,7 +1911,6 @@ fn split(settings: &Settings) -> UResult<()> { // indicate that. A special error message needs to be // printed in that case. ErrorKind::Other => Err(USimpleError::new(1, format!("{e}"))), - ErrorKind::BrokenPipe => Ok(()), _ => Err(uio_error!(e, "input/output error")), }, } @@ -1813,7 +1929,6 @@ fn split(settings: &Settings) -> UResult<()> { // indicate that. A special error message needs to be // printed in that case. ErrorKind::Other => Err(USimpleError::new(1, format!("{e}"))), - ErrorKind::BrokenPipe => Ok(()), _ => Err(uio_error!(e, "input/output error")), }, } diff --git a/tests/by-util/test_split.rs b/tests/by-util/test_split.rs index be3933df38d..5760be56097 100644 --- a/tests/by-util/test_split.rs +++ b/tests/by-util/test_split.rs @@ -338,6 +338,36 @@ fn test_filter_broken_pipe() { .succeeds(); } +#[test] +#[cfg(unix)] +fn test_filter_with_kth_chunk() { + let scene = TestScenario::new(util_name!()); + scene + .ucmd() + .args(&["--filter='some'", "--number=1/2"]) + .ignore_stdin_write_error() + .pipe_in("a\n") + .fails() + .no_stdout() + .stderr_contains("--filter does not process a chunk extracted to stdout"); + scene + .ucmd() + .args(&["--filter='some'", "--number=l/1/2"]) + .ignore_stdin_write_error() + .pipe_in("a\n") + .fails() + .no_stdout() + .stderr_contains("--filter does not process a chunk extracted to stdout"); + scene + .ucmd() + .args(&["--filter='some'", "--number=r/1/2"]) + .ignore_stdin_write_error() + .pipe_in("a\n") + .fails() + .no_stdout() + .stderr_contains("--filter does not process a chunk extracted to stdout"); +} + #[test] fn test_split_lines_number() { // Test if stdout/stderr for '--lines' option is correct @@ -699,6 +729,24 @@ fn test_split_stdin_num_kth_chunk() { .stderr_only("split: -: cannot determine file size\n"); } +#[test] +fn test_split_stdin_num_line_chunks() { + new_ucmd!() + .args(&["--number=l/2"]) + .fails() + .code_is(1) + .stderr_only("split: -: cannot determine file size\n"); +} + +#[test] +fn test_split_stdin_num_kth_line_chunk() { + new_ucmd!() + .args(&["--number=l/2/5"]) + .fails() + .code_is(1) + .stderr_only("split: -: cannot determine file size\n"); +} + fn file_read(at: &AtPath, filename: &str) -> String { let mut s = String::new(); at.open(filename).read_to_string(&mut s).unwrap(); From 772892e2e4dbbf22e88e5d4ca86d4560fefbc9c1 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Thu, 19 Oct 2023 17:05:29 +0200 Subject: [PATCH 0256/2851] cp: --rem don't fail if dest is symlink to source --- src/uu/cp/src/cp.rs | 8 ++++++++ tests/by-util/test_cp.rs | 16 ++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index f9f6d87634a..da9918365ff 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -1647,6 +1647,14 @@ fn copy_file( dest.display() ))); } + if paths_refer_to_same_file(source, dest, true) + && matches!( + options.overwrite, + OverwriteMode::Clobber(ClobberMode::RemoveDestination) + ) + { + fs::remove_file(dest)?; + } } if file_or_link_exists(dest) { diff --git a/tests/by-util/test_cp.rs b/tests/by-util/test_cp.rs index 9894087e8b2..b3cc3e0c12c 100644 --- a/tests/by-util/test_cp.rs +++ b/tests/by-util/test_cp.rs @@ -2827,6 +2827,22 @@ fn test_cp_mode_hardlink_no_dereference() { assert_eq!(at.read_symlink("z"), "slink"); } +#[test] +fn test_remove_destination_with_destination_being_a_symlink_to_source() { + let (at, mut ucmd) = at_and_ucmd!(); + let file = "file"; + let symlink = "symlink"; + + at.touch(file); + at.symlink_file(file, symlink); + + ucmd.args(&["--remove-destination", file, symlink]) + .succeeds(); + assert!(!at.symlink_exists(symlink)); + assert!(at.file_exists(file)); + assert!(at.file_exists(symlink)); +} + #[test] fn test_remove_destination_symbolic_link_loop() { let (at, mut ucmd) = at_and_ucmd!(); From 4472acf909b8d4598aefffb15ef440186698ebb5 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 1 Oct 2023 22:19:48 +0200 Subject: [PATCH 0257/2851] ls --dired: document the whole thing --- src/uu/ls/src/dired.rs | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/uu/ls/src/dired.rs b/src/uu/ls/src/dired.rs index 74732d37a9f..c73b11ae345 100644 --- a/src/uu/ls/src/dired.rs +++ b/src/uu/ls/src/dired.rs @@ -4,6 +4,36 @@ // file that was distributed with this source code. // spell-checker:ignore dired subdired +/// `dired` Module Documentation +/// +/// This module handles the --dired output format, representing file and +/// directory listings. +/// +/// Key Mechanisms: +/// 1. **Position Tracking**: +/// - The module tracks byte positions for each file or directory entry. +/// - `BytePosition`: Represents a byte range with start and end positions. +/// - `DiredOutput`: Contains positions for DIRED and SUBDIRED outputs and +/// maintains a padding value. +/// +/// 2. **Padding**: +/// - Padding is used when dealing with directory names or the "total" line. +/// - The module adjusts byte positions by adding padding for these cases. +/// - This ensures correct offset for subsequent files or directories. +/// +/// 3. **Position Calculation**: +/// - Functions like `calculate_dired`, `calculate_subdired`, and +/// `calculate_and_update_positions` compute byte positions based on output +/// length, previous positions, and padding. +/// +/// 4. **Output**: +/// - The module provides functions to print the DIRED output +/// (`print_dired_output`) based on calculated positions and configuration. +/// - Helpers like `print_positions` print positions with specific prefixes. +/// +/// Overall, the module ensures each entry in the DIRED output has the correct +/// byte position, considering additional lines or padding affecting positions. +/// use crate::Config; use std::fmt; use std::io::{BufWriter, Stdout, Write}; From fa265b0520f735bf4cb5e80c01dfa8626901e092 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 22 Oct 2023 10:47:17 +0200 Subject: [PATCH 0258/2851] rm: adjust fail-2eperm.sh - gnu test --- util/build-gnu.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/util/build-gnu.sh b/util/build-gnu.sh index e2c67539441..697c8c53795 100755 --- a/util/build-gnu.sh +++ b/util/build-gnu.sh @@ -189,6 +189,8 @@ grep -rlE '/usr/local/bin/\s?/usr/local/bin' init.cfg tests/* | xargs --no-run-i sed -i -e "s|rm: cannot remove 'e/slink'|rm: cannot remove 'e'|g" tests/rm/fail-eacces.sh +sed -i -e "s|rm: cannot remove 'a/b'|rm: cannot remove 'a'|g" tests/rm/fail-2eperm.sh + sed -i -e "s|rm: cannot remove 'a/b/file'|rm: cannot remove 'a'|g" tests/rm/cycle.sh sed -i -e "s|rm: cannot remove directory 'b/a/p'|rm: cannot remove 'b'|g" tests/rm/rm1.sh From e3e0619e28b8a949c86c4ae7002a98d618752f7a Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Sun, 22 Oct 2023 17:04:34 +0200 Subject: [PATCH 0259/2851] doc: mention "-v/--verbose" as extension of du --- docs/src/extensions.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/src/extensions.md b/docs/src/extensions.md index eeb00ff350e..79746498f2d 100644 --- a/docs/src/extensions.md +++ b/docs/src/extensions.md @@ -77,4 +77,5 @@ third way: `--long`. ## `du` -`du` allows `birth` and `creation` as values for the `--time` argument to show the creation time. +`du` allows `birth` and `creation` as values for the `--time` argument to show the creation time. It +also provides a `-v`/`--verbose` flag. From 897af02d9da7a3d80e4b118537bda81c94783451 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 22 Oct 2023 21:43:46 +0200 Subject: [PATCH 0260/2851] runcon: generate the same error as GNUs tested in tests/runcon/runcon-no-reorder.sh --- src/uu/runcon/src/errors.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/runcon/src/errors.rs b/src/uu/runcon/src/errors.rs index 5b628294060..382ab3bed36 100644 --- a/src/uu/runcon/src/errors.rs +++ b/src/uu/runcon/src/errors.rs @@ -26,7 +26,7 @@ pub(crate) enum Error { #[error("No command is specified")] MissingCommand, - #[error("SELinux is not enabled")] + #[error("runcon may be used only on a SELinux kernel")] SELinuxNotEnabled, #[error(transparent)] From 43f14e4126414486798566fb3d32e3ee31374bbe Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 22 Oct 2023 22:11:34 +0200 Subject: [PATCH 0261/2851] pr: skip a test for now for way too long log --- util/build-gnu.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/util/build-gnu.sh b/util/build-gnu.sh index 697c8c53795..35e6d5b6bf1 100755 --- a/util/build-gnu.sh +++ b/util/build-gnu.sh @@ -208,6 +208,10 @@ sed -i -e "s|---dis ||g" tests/tail/overlay-headers.sh test -f "${UU_BUILD_DIR}/getlimits" || cp src/getlimits "${UU_BUILD_DIR}" +# pr produces very long log and this command isn't super interesting +# SKIP for now +sed -i -e "s|my \$prog = 'pr';$|my \$prog = 'pr';CuSkip::skip \"\$prog: SKIP for producing too long logs\";|" tests/pr/pr-tests.pl + # When decoding an invalid base32/64 string, gnu writes everything it was able to decode until # it hit the decode error, while we don't write anything if the input is invalid. sed -i "s/\(baddecode.*OUT=>\"\).*\"/\1\"/g" tests/misc/base64.pl From 94492c98a52bbb9b406ebe9599319796553d5300 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Fri, 20 Oct 2023 09:40:09 +0200 Subject: [PATCH 0262/2851] cp: --rem don't fail if dest is hardlink to source --- src/uu/cp/src/cp.rs | 13 +++++++++++-- tests/by-util/test_cp.rs | 18 ++++++++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index da9918365ff..5441e88ed39 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -33,8 +33,8 @@ use platform::copy_on_write; use uucore::display::Quotable; use uucore::error::{set_exit_code, UClapError, UError, UResult, UUsageError}; use uucore::fs::{ - canonicalize, is_symlink_loop, paths_refer_to_same_file, FileInformation, MissingHandling, - ResolveMode, + are_hardlinks_to_same_file, canonicalize, is_symlink_loop, paths_refer_to_same_file, + FileInformation, MissingHandling, ResolveMode, }; use uucore::{backup_control, update_control}; // These are exposed for projects (e.g. nushell) that want to create an `Options` value, which @@ -1657,6 +1657,15 @@ fn copy_file( } } + if are_hardlinks_to_same_file(source, dest) + && matches!( + options.overwrite, + OverwriteMode::Clobber(ClobberMode::RemoveDestination) + ) + { + fs::remove_file(dest)?; + } + if file_or_link_exists(dest) { handle_existing_dest(source, dest, options, source_in_command_line)?; } diff --git a/tests/by-util/test_cp.rs b/tests/by-util/test_cp.rs index b3cc3e0c12c..7d3ebfa709c 100644 --- a/tests/by-util/test_cp.rs +++ b/tests/by-util/test_cp.rs @@ -2827,6 +2827,24 @@ fn test_cp_mode_hardlink_no_dereference() { assert_eq!(at.read_symlink("z"), "slink"); } +#[cfg(not(any(windows, target_os = "android")))] +#[test] +fn test_remove_destination_with_destination_being_a_hardlink_to_source() { + let (at, mut ucmd) = at_and_ucmd!(); + let file = "file"; + let hardlink = "hardlink"; + + at.touch(file); + at.hard_link(file, hardlink); + + ucmd.args(&["--remove-destination", file, hardlink]) + .succeeds(); + + assert_eq!("", at.resolve_link(hardlink)); + assert!(at.file_exists(file)); + assert!(at.file_exists(hardlink)); +} + #[test] fn test_remove_destination_with_destination_being_a_symlink_to_source() { let (at, mut ucmd) = at_and_ucmd!(); From dbfd700502cdd0beac32e7ab3cb9397c9f4d6b99 Mon Sep 17 00:00:00 2001 From: Nathan Houghton Date: Sun, 22 Oct 2023 10:51:15 -0700 Subject: [PATCH 0263/2851] test: use mtime for -ot and fix direction of comparison - Use the file modification time instead of the creation time (matches GNU coreutils documentation) - Fix direction of comparison (a < b instead of a > b) - Extend test case to cover both the 0 and 1 exit code cases --- src/uu/test/src/test.rs | 2 +- tests/by-util/test_test.rs | 18 ++++++++++++++---- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/uu/test/src/test.rs b/src/uu/test/src/test.rs index 7e778f9b5e0..4f230a590df 100644 --- a/src/uu/test/src/test.rs +++ b/src/uu/test/src/test.rs @@ -218,7 +218,7 @@ fn files(a: &OsStr, b: &OsStr, op: &OsStr) -> ParseResult { #[cfg(not(unix))] Some("-ef") => unimplemented!(), Some("-nt") => f_a.modified().unwrap() > f_b.modified().unwrap(), - Some("-ot") => f_a.created().unwrap() > f_b.created().unwrap(), + Some("-ot") => f_a.modified().unwrap() < f_b.modified().unwrap(), _ => return Err(ParseError::UnknownOperator(op.quote().to_string())), }) } diff --git a/tests/by-util/test_test.rs b/tests/by-util/test_test.rs index 2f36a805c36..922d854c640 100644 --- a/tests/by-util/test_test.rs +++ b/tests/by-util/test_test.rs @@ -317,7 +317,7 @@ fn test_file_is_itself() { } #[test] -#[cfg(not(any(target_env = "musl", target_os = "android")))] +#[cfg(not(target_os = "android"))] fn test_file_is_newer_than_and_older_than_itself() { // odd but matches GNU new_ucmd!() @@ -364,8 +364,7 @@ fn test_same_device_inode() { } #[test] -#[cfg(not(any(target_env = "musl", target_os = "android")))] -// musl: creation time is not available on this platform currently +#[cfg(not(target_os = "android"))] fn test_newer_file() { let scenario = TestScenario::new(util_name!()); @@ -377,10 +376,21 @@ fn test_newer_file() { .ucmd() .args(&["newer_file", "-nt", "regular_file"]) .succeeds(); + scenario .ucmd() - .args(&["newer_file", "-ot", "regular_file"]) + .args(&["regular_file", "-nt", "newer_file"]) + .fails(); + + scenario + .ucmd() + .args(&["regular_file", "-ot", "newer_file"]) .succeeds(); + + scenario + .ucmd() + .args(&["newer_file", "-ot", "regular_file"]) + .fails(); } #[test] From e6dd1045f05569d4cf29877a61af10f1e3e3a737 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 23 Oct 2023 07:26:01 +0000 Subject: [PATCH 0264/2851] chore(deps): update rust crate bytecount to 0.6.5 --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6c6926db9ea..e42d18a79c0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -205,9 +205,9 @@ checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" [[package]] name = "bytecount" -version = "0.6.4" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad152d03a2c813c80bb94fedbf3a3f02b28f793e39e7c214c8a0bcc196343de7" +checksum = "d1a12477b7237a01c11a80a51278165f9ba0edd28fa6db00a65ab230320dc58c" [[package]] name = "byteorder" diff --git a/Cargo.toml b/Cargo.toml index 6b412488cd3..4dd8b2a1940 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -260,7 +260,7 @@ test = ["uu_test"] bigdecimal = "0.4" binary-heap-plus = "0.5.0" bstr = "1.7" -bytecount = "0.6.4" +bytecount = "0.6.5" byteorder = "1.5.0" chrono = { version = "^0.4.31", default-features = false, features = [ "std", From 937f29b80734c8dceca19f70dcc74a1082c09948 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 22 Oct 2023 21:26:11 +0200 Subject: [PATCH 0265/2851] ls: update of the GNU test error output --- util/build-gnu.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/util/build-gnu.sh b/util/build-gnu.sh index 35e6d5b6bf1..0af07266b93 100755 --- a/util/build-gnu.sh +++ b/util/build-gnu.sh @@ -272,6 +272,10 @@ sed -i -e "s/du: invalid -t argument/du: invalid --threshold argument/" -e "s/du # with ls --dired, in case of error, we have a slightly different error position sed -i -e "s|44 45|48 49|" tests/ls/stat-failed.sh +# small difference in the error message +sed -i -e "/ls: invalid argument 'XX' for 'time style'/,/Try 'ls --help' for more information\./c\ +ls: invalid --time-style argument 'XX'\nPossible values are: [\"full-iso\", \"long-iso\", \"iso\", \"locale\", \"+FORMAT (e.g., +%H:%M) for a 'date'-style format\"]\n\nFor more information try --help" tests/ls/time-style-diag.sh + # disable two kind of tests: # "hostid BEFORE --help" doesn't fail for GNU. we fail. we are probably doing better # "hostid BEFORE --help AFTER " same for this From a3fed79b5ce384bd0c2b9996d1cc8ed1b52f7e45 Mon Sep 17 00:00:00 2001 From: Yury Zhytkou <54360928+zhitkoff@users.noreply.github.com> Date: Mon, 23 Oct 2023 16:47:39 -0400 Subject: [PATCH 0266/2851] `split`: suffix auto length (#5433) --- src/uu/split/src/filenames.rs | 42 +++++++---- src/uu/split/src/split.rs | 132 +++++++++++++++++++++++++--------- 2 files changed, 127 insertions(+), 47 deletions(-) diff --git a/src/uu/split/src/filenames.rs b/src/uu/split/src/filenames.rs index 08f08e293f0..e6a9f19b2b7 100644 --- a/src/uu/split/src/filenames.rs +++ b/src/uu/split/src/filenames.rs @@ -121,9 +121,10 @@ impl<'a> FilenameIterator<'a> { suffix_length: usize, suffix_type: SuffixType, suffix_start: usize, + suffix_auto_widening: bool, ) -> UResult> { let radix = suffix_type.radix(); - let number = if suffix_length == 0 { + let number = if suffix_auto_widening { Number::DynamicWidth(DynamicWidthNumber::new(radix, suffix_start)) } else { Number::FixedWidth( @@ -171,36 +172,42 @@ mod tests { #[test] fn test_filename_iterator_alphabetic_fixed_width() { - let mut it = FilenameIterator::new("chunk_", ".txt", 2, SuffixType::Alphabetic, 0).unwrap(); + let mut it = + FilenameIterator::new("chunk_", ".txt", 2, SuffixType::Alphabetic, 0, false).unwrap(); assert_eq!(it.next().unwrap(), "chunk_aa.txt"); assert_eq!(it.next().unwrap(), "chunk_ab.txt"); assert_eq!(it.next().unwrap(), "chunk_ac.txt"); - let mut it = FilenameIterator::new("chunk_", ".txt", 2, SuffixType::Alphabetic, 0).unwrap(); + let mut it = + FilenameIterator::new("chunk_", ".txt", 2, SuffixType::Alphabetic, 0, false).unwrap(); assert_eq!(it.nth(26 * 26 - 1).unwrap(), "chunk_zz.txt"); assert_eq!(it.next(), None); } #[test] fn test_filename_iterator_numeric_fixed_width() { - let mut it = FilenameIterator::new("chunk_", ".txt", 2, SuffixType::Decimal, 0).unwrap(); + let mut it = + FilenameIterator::new("chunk_", ".txt", 2, SuffixType::Decimal, 0, false).unwrap(); assert_eq!(it.next().unwrap(), "chunk_00.txt"); assert_eq!(it.next().unwrap(), "chunk_01.txt"); assert_eq!(it.next().unwrap(), "chunk_02.txt"); - let mut it = FilenameIterator::new("chunk_", ".txt", 2, SuffixType::Decimal, 0).unwrap(); + let mut it = + FilenameIterator::new("chunk_", ".txt", 2, SuffixType::Decimal, 0, false).unwrap(); assert_eq!(it.nth(10 * 10 - 1).unwrap(), "chunk_99.txt"); assert_eq!(it.next(), None); } #[test] fn test_filename_iterator_alphabetic_dynamic_width() { - let mut it = FilenameIterator::new("chunk_", ".txt", 0, SuffixType::Alphabetic, 0).unwrap(); + let mut it = + FilenameIterator::new("chunk_", ".txt", 2, SuffixType::Alphabetic, 0, true).unwrap(); assert_eq!(it.next().unwrap(), "chunk_aa.txt"); assert_eq!(it.next().unwrap(), "chunk_ab.txt"); assert_eq!(it.next().unwrap(), "chunk_ac.txt"); - let mut it = FilenameIterator::new("chunk_", ".txt", 0, SuffixType::Alphabetic, 0).unwrap(); + let mut it = + FilenameIterator::new("chunk_", ".txt", 2, SuffixType::Alphabetic, 0, true).unwrap(); assert_eq!(it.nth(26 * 25 - 1).unwrap(), "chunk_yz.txt"); assert_eq!(it.next().unwrap(), "chunk_zaaa.txt"); assert_eq!(it.next().unwrap(), "chunk_zaab.txt"); @@ -208,12 +215,14 @@ mod tests { #[test] fn test_filename_iterator_numeric_dynamic_width() { - let mut it = FilenameIterator::new("chunk_", ".txt", 0, SuffixType::Decimal, 0).unwrap(); + let mut it = + FilenameIterator::new("chunk_", ".txt", 2, SuffixType::Decimal, 0, true).unwrap(); assert_eq!(it.next().unwrap(), "chunk_00.txt"); assert_eq!(it.next().unwrap(), "chunk_01.txt"); assert_eq!(it.next().unwrap(), "chunk_02.txt"); - let mut it = FilenameIterator::new("chunk_", ".txt", 0, SuffixType::Decimal, 0).unwrap(); + let mut it = + FilenameIterator::new("chunk_", ".txt", 2, SuffixType::Decimal, 0, true).unwrap(); assert_eq!(it.nth(10 * 9 - 1).unwrap(), "chunk_89.txt"); assert_eq!(it.next().unwrap(), "chunk_9000.txt"); assert_eq!(it.next().unwrap(), "chunk_9001.txt"); @@ -221,7 +230,8 @@ mod tests { #[test] fn test_filename_iterator_numeric_suffix_decimal() { - let mut it = FilenameIterator::new("chunk_", ".txt", 0, SuffixType::Decimal, 5).unwrap(); + let mut it = + FilenameIterator::new("chunk_", ".txt", 2, SuffixType::Decimal, 5, true).unwrap(); assert_eq!(it.next().unwrap(), "chunk_05.txt"); assert_eq!(it.next().unwrap(), "chunk_06.txt"); assert_eq!(it.next().unwrap(), "chunk_07.txt"); @@ -230,7 +240,7 @@ mod tests { #[test] fn test_filename_iterator_numeric_suffix_hex() { let mut it = - FilenameIterator::new("chunk_", ".txt", 0, SuffixType::Hexadecimal, 9).unwrap(); + FilenameIterator::new("chunk_", ".txt", 2, SuffixType::Hexadecimal, 9, true).unwrap(); assert_eq!(it.next().unwrap(), "chunk_09.txt"); assert_eq!(it.next().unwrap(), "chunk_0a.txt"); assert_eq!(it.next().unwrap(), "chunk_0b.txt"); @@ -238,19 +248,21 @@ mod tests { #[test] fn test_filename_iterator_numeric_suffix_err() { - let mut it = FilenameIterator::new("chunk_", ".txt", 3, SuffixType::Decimal, 999).unwrap(); + let mut it = + FilenameIterator::new("chunk_", ".txt", 3, SuffixType::Decimal, 999, false).unwrap(); assert_eq!(it.next().unwrap(), "chunk_999.txt"); assert!(it.next().is_none()); - let it = FilenameIterator::new("chunk_", ".txt", 3, SuffixType::Decimal, 1000); + let it = FilenameIterator::new("chunk_", ".txt", 3, SuffixType::Decimal, 1000, false); assert!(it.is_err()); let mut it = - FilenameIterator::new("chunk_", ".txt", 3, SuffixType::Hexadecimal, 0xfff).unwrap(); + FilenameIterator::new("chunk_", ".txt", 3, SuffixType::Hexadecimal, 0xfff, false) + .unwrap(); assert_eq!(it.next().unwrap(), "chunk_fff.txt"); assert!(it.next().is_none()); - let it = FilenameIterator::new("chunk_", ".txt", 3, SuffixType::Hexadecimal, 0x1000); + let it = FilenameIterator::new("chunk_", ".txt", 3, SuffixType::Hexadecimal, 0x1000, false); assert!(it.is_err()); } } diff --git a/src/uu/split/src/split.rs b/src/uu/split/src/split.rs index 71b6f34dc2d..fff1ccb65ac 100644 --- a/src/uu/split/src/split.rs +++ b/src/uu/split/src/split.rs @@ -37,7 +37,8 @@ static OPT_NUMERIC_SUFFIXES_SHORT: &str = "-d"; static OPT_HEX_SUFFIXES: &str = "hex-suffixes"; static OPT_HEX_SUFFIXES_SHORT: &str = "-x"; static OPT_SUFFIX_LENGTH: &str = "suffix-length"; -static OPT_DEFAULT_SUFFIX_LENGTH: &str = "0"; +// If no suffix length is specified, default to "2" characters following GNU split behavior +static OPT_DEFAULT_SUFFIX_LENGTH: &str = "2"; static OPT_VERBOSE: &str = "verbose"; static OPT_SEPARATOR: &str = "separator"; //The ---io and ---io-blksize parameters are consumed and ignored. @@ -313,7 +314,6 @@ pub fn uu_app() -> Command { .long(OPT_NUMERIC_SUFFIXES) .alias("numeric") .require_equals(true) - .default_missing_value("0") .num_args(0..=1) .overrides_with_all([ OPT_NUMERIC_SUFFIXES, @@ -340,7 +340,6 @@ pub fn uu_app() -> Command { Arg::new(OPT_HEX_SUFFIXES) .long(OPT_HEX_SUFFIXES) .alias("hex") - .default_missing_value("0") .require_equals(true) .num_args(0..=1) .overrides_with_all([ @@ -359,7 +358,7 @@ pub fn uu_app() -> Command { .allow_hyphen_values(true) .value_name("N") .default_value(OPT_DEFAULT_SUFFIX_LENGTH) - .help("use suffixes of fixed length N. 0 implies dynamic length, starting with 2"), + .help("generate suffixes of length N (default 2)"), ) .arg( Arg::new(OPT_VERBOSE) @@ -669,35 +668,99 @@ impl Strategy { } } -/// Parse the suffix type from the command-line arguments. -fn suffix_type_from(matches: &ArgMatches) -> Result<(SuffixType, usize), SettingsError> { +/// Parse the suffix type, start and length from the command-line arguments. +/// Determine if the output file names suffix is allowed to auto-widen, +/// i.e. go beyond suffix_length, when more output files need to be written into. +/// Suffix auto-widening rules are: +/// - OFF when suffix length N is specified +/// `-a N` or `--suffix-length=N` +/// - OFF when suffix start number N is specified using long option with value +/// `--numeric-suffixes=N` or `--hex-suffixes=N` +/// - Exception to the above: ON with `-n`/`--number` option (N, K/N, l/N, l/K/N, r/N, r/K/N) +/// and suffix start < N number of files +/// - ON when suffix start number is NOT specified +fn suffix_from( + matches: &ArgMatches, + strategy: &Strategy, +) -> Result<(SuffixType, usize, bool, usize), SettingsError> { + let suffix_type: SuffixType; + + // Defaults + let mut suffix_start = 0; + let mut suffix_auto_widening = true; + // Check if the user is specifying one or more than one suffix // Any combination of suffixes is allowed // Since all suffixes are setup with 'overrides_with_all()' against themselves and each other, // last one wins, all others are ignored match ( - matches.get_one::(OPT_NUMERIC_SUFFIXES), - matches.get_one::(OPT_HEX_SUFFIXES), + matches.contains_id(OPT_NUMERIC_SUFFIXES), + matches.contains_id(OPT_HEX_SUFFIXES), matches.get_flag(OPT_NUMERIC_SUFFIXES_SHORT), matches.get_flag(OPT_HEX_SUFFIXES_SHORT), ) { - (Some(v), _, _, _) => { - let suffix_start = v; - let suffix_start = suffix_start - .parse::() - .map_err(|_| SettingsError::SuffixNotParsable(suffix_start.to_string()))?; - Ok((SuffixType::Decimal, suffix_start)) + (true, _, _, _) => { + suffix_type = SuffixType::Decimal; + let suffix_opt = matches.get_one::(OPT_NUMERIC_SUFFIXES); // if option was specified, but without value - this will return None as there is no default value + if suffix_opt.is_some() { + (suffix_start, suffix_auto_widening) = + handle_long_suffix_opt(suffix_opt.unwrap(), strategy, false)?; + } } - (_, Some(v), _, _) => { - let suffix_start = v; - let suffix_start = usize::from_str_radix(suffix_start, 16) - .map_err(|_| SettingsError::SuffixNotParsable(suffix_start.to_string()))?; - Ok((SuffixType::Hexadecimal, suffix_start)) + (_, true, _, _) => { + suffix_type = SuffixType::Hexadecimal; + let suffix_opt = matches.get_one::(OPT_HEX_SUFFIXES); // if option was specified, but without value - this will return None as there is no default value + if suffix_opt.is_some() { + (suffix_start, suffix_auto_widening) = + handle_long_suffix_opt(suffix_opt.unwrap(), strategy, true)?; + } } - (_, _, true, _) => Ok((SuffixType::Decimal, 0)), // short numeric suffix '-d', default start 0 - (_, _, _, true) => Ok((SuffixType::Hexadecimal, 0)), // short hex suffix '-x', default start 0 - _ => Ok((SuffixType::Alphabetic, 0)), // no numeric/hex suffix, using default alphabetic + (_, _, true, _) => suffix_type = SuffixType::Decimal, // short numeric suffix '-d' + (_, _, _, true) => suffix_type = SuffixType::Hexadecimal, // short hex suffix '-x' + _ => suffix_type = SuffixType::Alphabetic, // no numeric/hex suffix, using default alphabetic + } + + let suffix_length_str = matches.get_one::(OPT_SUFFIX_LENGTH).unwrap(); // safe to unwrap here as there is default value for this option + let suffix_length: usize = suffix_length_str + .parse() + .map_err(|_| SettingsError::SuffixNotParsable(suffix_length_str.to_string()))?; + + // Override suffix_auto_widening if suffix length value came from command line + // and not from default value + if matches.value_source(OPT_SUFFIX_LENGTH) == Some(ValueSource::CommandLine) { + suffix_auto_widening = false; } + + Ok(( + suffix_type, + suffix_start, + suffix_auto_widening, + suffix_length, + )) +} + +/// Helper function to [`suffix_from`] function +fn handle_long_suffix_opt( + suffix_opt: &String, + strategy: &Strategy, + is_hex: bool, +) -> Result<(usize, bool), SettingsError> { + let suffix_start = if is_hex { + usize::from_str_radix(suffix_opt, 16) + .map_err(|_| SettingsError::SuffixNotParsable(suffix_opt.to_string()))? + } else { + suffix_opt + .parse::() + .map_err(|_| SettingsError::SuffixNotParsable(suffix_opt.to_string()))? + }; + + let suffix_auto_widening = if let Strategy::Number(ref number_type) = strategy { + let chunks = number_type.num_chunks(); + (suffix_start as u64) < chunks + } else { + false + }; + Ok((suffix_start, suffix_auto_widening)) } /// Parameters that control how a file gets split. @@ -709,6 +772,8 @@ struct Settings { suffix_type: SuffixType, suffix_length: usize, suffix_start: usize, + /// Whether or not suffix length should automatically widen + suffix_auto_widening: bool, additional_suffix: String, input: String, /// When supplied, a shell command to output to instead of xaa, xab … @@ -809,14 +874,12 @@ impl Settings { return Err(SettingsError::SuffixContainsSeparator(additional_suffix)); } let strategy = Strategy::from(matches, obs_lines).map_err(SettingsError::Strategy)?; - let (suffix_type, suffix_start) = suffix_type_from(matches)?; - let suffix_length_str = matches.get_one::(OPT_SUFFIX_LENGTH).unwrap(); - let suffix_length: usize = suffix_length_str - .parse() - .map_err(|_| SettingsError::SuffixNotParsable(suffix_length_str.to_string()))?; + let (suffix_type, suffix_start, suffix_auto_widening, suffix_length) = + suffix_from(matches, &strategy)?; + if let Strategy::Number(ref number_type) = strategy { let chunks = number_type.num_chunks(); - if suffix_length != 0 { + if !suffix_auto_widening { let required_suffix_length = (chunks as f64).log(suffix_type.radix() as f64).ceil() as usize; if suffix_length < required_suffix_length { @@ -845,13 +908,12 @@ impl Settings { }; let result = Self { - suffix_length: suffix_length_str - .parse() - .map_err(|_| SettingsError::SuffixNotParsable(suffix_length_str.to_string()))?, + suffix_length, suffix_type, suffix_start, + suffix_auto_widening, additional_suffix, - verbose: matches.value_source("verbose") == Some(ValueSource::CommandLine), + verbose: matches.value_source(OPT_VERBOSE) == Some(ValueSource::CommandLine), separator, strategy, input: matches.get_one::(ARG_INPUT).unwrap().to_owned(), @@ -979,6 +1041,7 @@ impl<'a> ByteChunkWriter<'a> { settings.suffix_length, settings.suffix_type, settings.suffix_start, + settings.suffix_auto_widening, )?; let filename = filename_iterator .next() @@ -1109,6 +1172,7 @@ impl<'a> LineChunkWriter<'a> { settings.suffix_length, settings.suffix_type, settings.suffix_start, + settings.suffix_auto_widening, )?; let filename = filename_iterator .next() @@ -1222,6 +1286,7 @@ impl<'a> LineBytesChunkWriter<'a> { settings.suffix_length, settings.suffix_type, settings.suffix_start, + settings.suffix_auto_widening, )?; let filename = filename_iterator .next() @@ -1445,6 +1510,7 @@ where settings.suffix_length, settings.suffix_type, settings.suffix_start, + settings.suffix_auto_widening, )?; // Create one writer for each chunk. This will create each @@ -1616,6 +1682,7 @@ where settings.suffix_length, settings.suffix_type, settings.suffix_start, + settings.suffix_auto_widening, )?; // Create one writer for each chunk. This will create each @@ -1757,6 +1824,7 @@ where settings.suffix_length, settings.suffix_type, settings.suffix_start, + settings.suffix_auto_widening, ) .map_err(|e| io::Error::new(ErrorKind::Other, format!("{e}")))?; From c7c8c748d13edaeae6ad4f5ae30aeb3110ef88c0 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Tue, 24 Oct 2023 07:29:06 +0200 Subject: [PATCH 0267/2851] Bump const-random from 0.1.15 to 0.1.16 --- Cargo.lock | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e42d18a79c0..333cbd1861d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -333,23 +333,21 @@ dependencies = [ [[package]] name = "const-random" -version = "0.1.15" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368a7a772ead6ce7e1de82bfb04c485f3db8ec744f72925af5735e29a22cc18e" +checksum = "11df32a13d7892ec42d51d3d175faba5211ffe13ed25d4fb348ac9e9ce835593" dependencies = [ "const-random-macro", - "proc-macro-hack", ] [[package]] name = "const-random-macro" -version = "0.1.15" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d7d6ab3c3a2282db210df5f02c4dab6e0a7057af0fb7ebd4070f30fe05c0ddb" +checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" dependencies = [ "getrandom", "once_cell", - "proc-macro-hack", "tiny-keccak", ] @@ -1589,12 +1587,6 @@ dependencies = [ "yansi", ] -[[package]] -name = "proc-macro-hack" -version = "0.5.20+deprecated" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" - [[package]] name = "proc-macro2" version = "1.0.63" From d7b7dfeb16742f9855155d9ea413f56b3758c5c6 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Tue, 24 Oct 2023 10:07:28 +0200 Subject: [PATCH 0268/2851] ls: use try_get_matches_from instead of get_matches_from to replace clap's exit code --- src/uu/ls/src/ls.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 41d2f59b131..8ecf7982a08 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -1027,7 +1027,7 @@ impl Config { pub fn uumain(args: impl uucore::Args) -> UResult<()> { let command = uu_app(); - let matches = command.get_matches_from(args); + let matches = command.try_get_matches_from(args)?; let config = Config::from(&matches)?; From 769eb29cd3503d7734776572f7875a864c847c92 Mon Sep 17 00:00:00 2001 From: David Matos Date: Tue, 24 Oct 2023 10:54:23 +0200 Subject: [PATCH 0269/2851] mv: moving directory itself should fail (#5429) * mv: moving directory itself should fail * mv: Check trailing slash also fails on target containing itself * mv: add "spell-checker:ignore mydir" to test --------- Co-authored-by: Daniel Hofstetter --- src/uu/mv/src/error.rs | 5 +++++ src/uu/mv/src/mv.rs | 22 ++++++++++++++++++++++ tests/by-util/test_mv.rs | 25 +++++++++++++++++++++++++ 3 files changed, 52 insertions(+) diff --git a/src/uu/mv/src/error.rs b/src/uu/mv/src/error.rs index a6605e23234..e891fc2ec01 100644 --- a/src/uu/mv/src/error.rs +++ b/src/uu/mv/src/error.rs @@ -12,6 +12,7 @@ pub enum MvError { NoSuchFile(String), SameFile(String, String), SelfSubdirectory(String), + SelfTargetSubdirectory(String, String), DirectoryToNonDirectory(String), NonDirectoryToDirectory(String, String), NotADirectory(String), @@ -29,6 +30,10 @@ impl Display for MvError { f, "cannot move '{s}' to a subdirectory of itself, '{s}/{s}'" ), + Self::SelfTargetSubdirectory(s, t) => write!( + f, + "cannot move '{s}' to a subdirectory of itself, '{t}/{s}'" + ), Self::DirectoryToNonDirectory(t) => { write!(f, "cannot overwrite directory {t} with non-directory") } diff --git a/src/uu/mv/src/mv.rs b/src/uu/mv/src/mv.rs index 7236907da77..47e0b864d32 100644 --- a/src/uu/mv/src/mv.rs +++ b/src/uu/mv/src/mv.rs @@ -326,6 +326,28 @@ fn handle_two_paths(source: &Path, target: &Path, opts: &Options) -> UResult<()> Err(MvError::DirectoryToNonDirectory(target.quote().to_string()).into()) } } else { + // Check that source & target do not contain same subdir/dir when both exist + // mkdir dir1/dir2; mv dir1 dir1/dir2 + let target_contains_itself = target + .as_os_str() + .to_str() + .ok_or("not a valid unicode string") + .and_then(|t| { + source + .as_os_str() + .to_str() + .ok_or("not a valid unicode string") + .map(|s| t.contains(s)) + }) + .unwrap(); + + if target_contains_itself { + return Err(MvError::SelfTargetSubdirectory( + source.display().to_string(), + target.display().to_string(), + ) + .into()); + } move_files_into_dir(&[source.to_path_buf()], target, opts) } } else if target.exists() && source.is_dir() { diff --git a/tests/by-util/test_mv.rs b/tests/by-util/test_mv.rs index f7f9622f52e..e8866732014 100644 --- a/tests/by-util/test_mv.rs +++ b/tests/by-util/test_mv.rs @@ -2,6 +2,8 @@ // // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. +// +// spell-checker:ignore mydir use crate::common::util::TestScenario; use filetime::FileTime; use std::thread::sleep; @@ -1389,6 +1391,29 @@ fn test_mv_into_self_data() { assert!(at.file_exists(file2)); assert!(!at.file_exists(file1)); } + +#[test] +fn test_mv_directory_into_subdirectory_of_itself_fails() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let dir1 = "mydir"; + let dir2 = "mydir/mydir_2"; + at.mkdir(dir1); + at.mkdir(dir2); + scene.ucmd().arg(dir1).arg(dir2).fails().stderr_contains( + "mv: cannot move 'mydir' to a subdirectory of itself, 'mydir/mydir_2/mydir'", + ); + + // check that it also errors out with / + scene + .ucmd() + .arg(format!("{}/", dir1)) + .arg(dir2) + .fails() + .stderr_contains( + "mv: cannot move 'mydir/' to a subdirectory of itself, 'mydir/mydir_2/mydir/'", + ); +} // Todo: // $ at.touch a b From fd18d2686f4d0a28dcf95a1db0f419cc82db699e Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Tue, 24 Oct 2023 14:48:24 +0200 Subject: [PATCH 0270/2851] ls: return exit code 2 for -l --dired --zero --- src/uu/ls/src/ls.rs | 18 ++++++++++++------ tests/by-util/test_ls.rs | 14 ++++++++++++++ 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 8ecf7982a08..db797741262 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -168,7 +168,8 @@ enum LsError { IOError(std::io::Error), IOErrorContext(std::io::Error, PathBuf, bool), BlockSizeParseError(String), - ConflictingArgumentDired(), + ConflictingArgumentDired, + DiredAndZeroAreIncompatible, AlreadyListedError(PathBuf), TimeStyleParseError(String, Vec), } @@ -181,7 +182,8 @@ impl UError for LsError { Self::IOErrorContext(_, _, false) => 1, Self::IOErrorContext(_, _, true) => 2, Self::BlockSizeParseError(_) => 1, - Self::ConflictingArgumentDired() => 1, + Self::ConflictingArgumentDired => 1, + Self::DiredAndZeroAreIncompatible => 2, Self::AlreadyListedError(_) => 2, Self::TimeStyleParseError(_, _) => 1, } @@ -196,10 +198,12 @@ impl Display for LsError { Self::BlockSizeParseError(s) => { write!(f, "invalid --block-size argument {}", s.quote()) } - Self::ConflictingArgumentDired() => { + Self::ConflictingArgumentDired => { write!(f, "--dired requires --format=long") } - + Self::DiredAndZeroAreIncompatible => { + write!(f, "--dired and --zero are incompatible") + } Self::TimeStyleParseError(s, possible_time_styles) => { write!( f, @@ -966,7 +970,10 @@ impl Config { let dired = options.get_flag(options::DIRED); if dired && format != Format::Long { - return Err(Box::new(LsError::ConflictingArgumentDired())); + return Err(Box::new(LsError::ConflictingArgumentDired)); + } + if dired && format == Format::Long && options.get_flag(options::ZERO) { + return Err(Box::new(LsError::DiredAndZeroAreIncompatible)); } let dereference = if options.get_flag(options::dereference::ALL) { @@ -1142,7 +1149,6 @@ pub fn uu_app() -> Command { .arg( Arg::new(options::ZERO) .long(options::ZERO) - .conflicts_with(options::DIRED) .overrides_with(options::ZERO) .help("List entries separated by ASCII NUL characters.") .action(ArgAction::SetTrue), diff --git a/tests/by-util/test_ls.rs b/tests/by-util/test_ls.rs index 15893b0e2ba..181c385e76e 100644 --- a/tests/by-util/test_ls.rs +++ b/tests/by-util/test_ls.rs @@ -3564,6 +3564,20 @@ fn test_ls_dired_incompatible() { .stderr_contains("--dired requires --format=long"); } +#[test] +fn test_ls_dired_and_zero_are_incompatible() { + let scene = TestScenario::new(util_name!()); + + scene + .ucmd() + .arg("--dired") + .arg("-l") + .arg("--zero") + .fails() + .code_is(2) + .stderr_contains("--dired and --zero are incompatible"); +} + #[test] fn test_ls_dired_recursive() { let scene = TestScenario::new(util_name!()); From 6f84e56e28ebb2c5fb246cba74031a9008503442 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Tue, 24 Oct 2023 16:33:04 +0200 Subject: [PATCH 0271/2851] ls: return exit code 2 for invalid time-style --- src/uu/ls/src/ls.rs | 2 +- tests/by-util/test_ls.rs | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 41d2f59b131..93574195929 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -183,7 +183,7 @@ impl UError for LsError { Self::BlockSizeParseError(_) => 1, Self::ConflictingArgumentDired() => 1, Self::AlreadyListedError(_) => 2, - Self::TimeStyleParseError(_, _) => 1, + Self::TimeStyleParseError(_, _) => 2, } } } diff --git a/tests/by-util/test_ls.rs b/tests/by-util/test_ls.rs index 15893b0e2ba..18deb1551e8 100644 --- a/tests/by-util/test_ls.rs +++ b/tests/by-util/test_ls.rs @@ -1737,7 +1737,12 @@ fn test_ls_styles() { .stdout_matches(&re_custom_format); // Also fails due to not having full clap support for time_styles - scene.ucmd().arg("-l").arg("-time-style=invalid").fails(); + scene + .ucmd() + .arg("-l") + .arg("--time-style=invalid") + .fails() + .code_is(2); //Overwrite options tests scene From 3bf1ed419533a9ab66254e509a55af34e8a7ad0f Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Tue, 24 Oct 2023 23:56:00 +0200 Subject: [PATCH 0272/2851] df: Replace the error message by the one generated by clap Failed with: -df: options OPT and --output are mutually exclusive -Try 'df --help' for more information. +error: the argument '--inodes' cannot be used with '--output[=...]' + +Usage: df [OPTION]... [FILE]... --- util/build-gnu.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/util/build-gnu.sh b/util/build-gnu.sh index 0af07266b93..7c0691c0697 100755 --- a/util/build-gnu.sh +++ b/util/build-gnu.sh @@ -269,6 +269,8 @@ sed -i -E "s|^([^#]*2_31.*)$|#\1|g" tests/printf/printf-cov.pl sed -i -e "s/du: invalid -t argument/du: invalid --threshold argument/" -e "s/du: option requires an argument/error: a value is required for '--threshold ' but none was supplied/" -e "/Try 'du --help' for more information./d" tests/du/threshold.sh +awk 'BEGIN {count=0} /compare exp out2/ && count < 6 {sub(/compare exp out2/, "grep -q \"cannot be used with\" out2"); count++} 1' tests/df/df-output.sh > tests/df/df-output.sh.tmp && mv tests/df/df-output.sh.tmp tests/df/df-output.sh + # with ls --dired, in case of error, we have a slightly different error position sed -i -e "s|44 45|48 49|" tests/ls/stat-failed.sh From e508b6e9df39bfdabe02504be53f7a0a3b8b452e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 24 Oct 2023 22:49:28 +0000 Subject: [PATCH 0273/2851] chore(deps): update rust crate bytecount to 0.6.7 --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 333cbd1861d..1e6aa238db0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -205,9 +205,9 @@ checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" [[package]] name = "bytecount" -version = "0.6.5" +version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1a12477b7237a01c11a80a51278165f9ba0edd28fa6db00a65ab230320dc58c" +checksum = "e1e5f035d16fc623ae5f74981db80a439803888314e3a555fd6f04acd51a3205" [[package]] name = "byteorder" diff --git a/Cargo.toml b/Cargo.toml index 4dd8b2a1940..a88fe5fc6f0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -260,7 +260,7 @@ test = ["uu_test"] bigdecimal = "0.4" binary-heap-plus = "0.5.0" bstr = "1.7" -bytecount = "0.6.5" +bytecount = "0.6.7" byteorder = "1.5.0" chrono = { version = "^0.4.31", default-features = false, features = [ "std", From db26dabd6eb803694fe51a55d7690cbd26060d2c Mon Sep 17 00:00:00 2001 From: Nathan Houghton Date: Sat, 21 Oct 2023 20:23:28 -0700 Subject: [PATCH 0274/2851] tests/dd: Do not use the OS provided dd utility on FIFOs On *BSD and macOS, the system provided dd utility opens up the output file for both reading and writing. This means that the open/write to the FIFO does not block, and almost instantly completes. The system dd then exits, leaving nothing left to be read by the time the coreutils-rs dd tries to open/read the FIFO. Avoid this problem by just writing to the FIFO from the test case itself, rather than relying on the system provide dd. --- tests/by-util/test_dd.rs | 72 ++++++++++++---------------------------- 1 file changed, 22 insertions(+), 50 deletions(-) diff --git a/tests/by-util/test_dd.rs b/tests/by-util/test_dd.rs index 8ebf57c1c84..f560e35261b 100644 --- a/tests/by-util/test_dd.rs +++ b/tests/by-util/test_dd.rs @@ -15,8 +15,6 @@ use regex::Regex; use std::fs::{File, OpenOptions}; use std::io::{BufReader, Read, Write}; use std::path::PathBuf; -#[cfg(all(unix, not(target_os = "macos"), not(target_os = "freebsd")))] -use std::process::{Command, Stdio}; #[cfg(not(windows))] use std::thread::sleep; #[cfg(not(windows))] @@ -1459,74 +1457,48 @@ fn test_sparse() { assert_eq!(at.metadata("infile").len(), at.metadata("outfile").len()); } -// TODO These FIFO tests should work on macos, but some issue is -// causing our implementation of dd to wait indefinitely when it -// shouldn't. - /// Test that a seek on an output FIFO results in a read. #[test] -#[cfg(all(unix, not(target_os = "macos"), not(target_os = "freebsd")))] +#[cfg(unix)] fn test_seek_output_fifo() { let ts = TestScenario::new(util_name!()); let at = &ts.fixtures; at.mkfifo("fifo"); - // TODO When `dd` is a bit more advanced, we could use the uutils - // version of dd here as well. - let child = Command::new("dd") - .current_dir(&at.subdir) - .args([ - "count=1", - "if=/dev/zero", - &format!("of={}", at.plus_as_string("fifo")), - "status=noxfer", - ]) - .stderr(Stdio::piped()) - .spawn() - .expect("failed to execute child process"); - - ts.ucmd() + let mut ucmd = ts.ucmd(); + let child = ucmd .args(&["count=0", "seek=1", "of=fifo", "status=noxfer"]) - .succeeds() - .stderr_only("0+0 records in\n0+0 records out\n"); + .run_no_wait(); - let output = child.wait_with_output().unwrap(); - assert!(output.status.success()); - assert!(output.stdout.is_empty()); - assert_eq!(&output.stderr, b"1+0 records in\n1+0 records out\n"); + std::fs::write(at.plus("fifo"), &vec![0; 512]).unwrap(); + + child + .wait() + .unwrap() + .success() + .stderr_only("0+0 records in\n0+0 records out\n"); } /// Test that a skip on an input FIFO results in a read. #[test] -#[cfg(all(unix, not(target_os = "macos"), not(target_os = "freebsd")))] +#[cfg(unix)] fn test_skip_input_fifo() { let ts = TestScenario::new(util_name!()); let at = &ts.fixtures; at.mkfifo("fifo"); - // TODO When `dd` is a bit more advanced, we could use the uutils - // version of dd here as well. - let child = Command::new("dd") - .current_dir(&at.subdir) - .args([ - "count=1", - "if=/dev/zero", - &format!("of={}", at.plus_as_string("fifo")), - "status=noxfer", - ]) - .stderr(Stdio::piped()) - .spawn() - .expect("failed to execute child process"); - - ts.ucmd() + let mut ucmd = ts.ucmd(); + let child = ucmd .args(&["count=0", "skip=1", "if=fifo", "status=noxfer"]) - .succeeds() - .stderr_only("0+0 records in\n0+0 records out\n"); + .run_no_wait(); + + std::fs::write(at.plus("fifo"), &vec![0; 512]).unwrap(); - let output = child.wait_with_output().unwrap(); - assert!(output.status.success()); - assert!(output.stdout.is_empty()); - assert_eq!(&output.stderr, b"1+0 records in\n1+0 records out\n"); + child + .wait() + .unwrap() + .success() + .stderr_only("0+0 records in\n0+0 records out\n"); } /// Test for reading part of stdin from each of two child processes. From 9df50096c867ba182d4b0c1b2b520ea9dd4c5a86 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Wed, 25 Oct 2023 10:15:46 +0200 Subject: [PATCH 0275/2851] cp: remove "all" from cfg; rename test fn --- tests/by-util/test_cp.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/by-util/test_cp.rs b/tests/by-util/test_cp.rs index 71c3eafb07a..a4922f93f7c 100644 --- a/tests/by-util/test_cp.rs +++ b/tests/by-util/test_cp.rs @@ -1552,8 +1552,8 @@ fn test_cp_preserve_links_case_7() { } #[test] -#[cfg(all(unix))] -fn test_cp_no_preserve_mode_case() { +#[cfg(unix)] +fn test_cp_no_preserve_mode() { use libc::umask; use uucore::fs as uufs; let (at, mut ucmd) = at_and_ucmd!(); From 086f7b548c525d7945f7edacbf1b3e7cea78c659 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Wed, 25 Oct 2023 10:20:01 +0200 Subject: [PATCH 0276/2851] cp: replace word in comment --- src/uu/cp/src/cp.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index 40b485c5c6b..4240af01f98 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -184,7 +184,7 @@ pub struct Attributes { #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Preserve { - // explicit means is the --no-preserve flag is used or not to distinguish out the default value. + // explicit means whether the --no-preserve flag is used or not to distinguish out the default value. // e.g. --no-preserve=mode means mode = No { explicit = true } No { explicit: bool }, Yes { required: bool }, From f8a30d524e1e5bd0c6f80a14293d9ad4ab83b4df Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Wed, 25 Oct 2023 10:43:23 +0200 Subject: [PATCH 0277/2851] cp: rename handling_no_preserve_mode to handle_no_preserve_mode --- src/uu/cp/src/cp.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index 4240af01f98..0ceef46e3a0 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -1743,7 +1743,7 @@ fn copy_file( let mut permissions = source_metadata.permissions(); #[cfg(unix)] { - let mut mode = handling_no_preserve_mode(options, permissions.mode()); + let mut mode = handle_no_preserve_mode(options, permissions.mode()); // apply umask use uucore::mode::get_umask; @@ -1876,7 +1876,7 @@ fn copy_file( } #[cfg(unix)] -fn handling_no_preserve_mode(options: &Options, org_mode: u32) -> u32 { +fn handle_no_preserve_mode(options: &Options, org_mode: u32) -> u32 { let (is_preserve_mode, is_explicit_no_preserve_mode) = options.preserve_mode(); if !is_preserve_mode { use libc::{ From 32b335a73a11cc9a43fa5075f56cbd76c1fcaab7 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Wed, 25 Oct 2023 16:38:01 +0200 Subject: [PATCH 0278/2851] mv: rename canonized_* -> canonicalized_* --- src/uu/mv/src/mv.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/uu/mv/src/mv.rs b/src/uu/mv/src/mv.rs index 47e0b864d32..5c52fef26d3 100644 --- a/src/uu/mv/src/mv.rs +++ b/src/uu/mv/src/mv.rs @@ -3,7 +3,7 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// spell-checker:ignore (ToDO) sourcepath targetpath nushell +// spell-checker:ignore (ToDO) sourcepath targetpath nushell canonicalized mod error; @@ -405,7 +405,7 @@ fn move_files_into_dir(files: &[PathBuf], target_dir: &Path, opts: &Options) -> return Err(MvError::NotADirectory(target_dir.quote().to_string()).into()); } - let canonized_target_dir = target_dir + let canonicalized_target_dir = target_dir .canonicalize() .unwrap_or_else(|_| target_dir.to_path_buf()); @@ -440,8 +440,8 @@ fn move_files_into_dir(files: &[PathBuf], target_dir: &Path, opts: &Options) -> // Check if we have mv dir1 dir2 dir2 // And generate an error if this is the case - if let Ok(canonized_source) = sourcepath.canonicalize() { - if canonized_source == canonized_target_dir { + if let Ok(canonicalized_source) = sourcepath.canonicalize() { + if canonicalized_source == canonicalized_target_dir { // User tried to move directory to itself, warning is shown // and process of moving files is continued. show!(USimpleError::new( @@ -450,7 +450,7 @@ fn move_files_into_dir(files: &[PathBuf], target_dir: &Path, opts: &Options) -> "cannot move '{}' to a subdirectory of itself, '{}/{}'", sourcepath.display(), target_dir.display(), - canonized_target_dir.components().last().map_or_else( + canonicalized_target_dir.components().last().map_or_else( || target_dir.display().to_string(), |dir| { PathBuf::from(dir.as_os_str()).display().to_string() } ) From 391b422ce1ec04922a299bab62339852e7198304 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 26 Oct 2023 23:04:43 +0000 Subject: [PATCH 0279/2851] chore(deps): update rust crate tempfile to 3.8.1 --- Cargo.lock | 14 +++++++------- Cargo.toml | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1e6aa238db0..164d6d6b7d8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1852,9 +1852,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.20" +version = "0.38.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67ce50cb2e16c2903e30d1cbccfd8387a74b9d4c938b6a4c5ec6cc7556f7a8a0" +checksum = "2b426b0506e5d50a7d8dafcf2e81471400deb602392c7dd110815afb4eaf02a3" dependencies = [ "bitflags 2.4.0", "errno", @@ -2066,14 +2066,14 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.8.0" +version = "3.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef" +checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5" dependencies = [ "cfg-if", "fastrand", - "redox_syscall 0.3.5", - "rustix 0.38.20", + "redox_syscall 0.4.0", + "rustix 0.38.21", "windows-sys 0.48.0", ] @@ -2093,7 +2093,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7" dependencies = [ - "rustix 0.38.20", + "rustix 0.38.21", "windows-sys 0.48.0", ] diff --git a/Cargo.toml b/Cargo.toml index a88fe5fc6f0..afddd49850c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -317,7 +317,7 @@ self_cell = "1.0.1" selinux = "0.4" signal-hook = "0.3.17" smallvec = { version = "1.11", features = ["union"] } -tempfile = "3.8.0" +tempfile = "3.8.1" uutils_term_grid = "0.3" terminal_size = "0.3.0" textwrap = { version = "0.16.0", features = ["terminal_size"] } From 9f5db291450d492dab8229aaac1465b79d4d6f66 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Thu, 26 Oct 2023 16:18:48 +0200 Subject: [PATCH 0280/2851] cp: add test for --attributes-only --- tests/by-util/test_cp.rs | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/tests/by-util/test_cp.rs b/tests/by-util/test_cp.rs index 4867c17eaf7..c79367afb2e 100644 --- a/tests/by-util/test_cp.rs +++ b/tests/by-util/test_cp.rs @@ -3499,3 +3499,33 @@ fn test_cp_dest_no_permissions() { .stderr_contains("invalid_perms.txt") .stderr_contains("denied"); } + +#[test] +#[cfg(all(unix, not(target_os = "freebsd")))] +fn test_cp_attributes_only() { + let (at, mut ucmd) = at_and_ucmd!(); + let a = "file_a"; + let b = "file_b"; + let mode_a = 0o0500; + let mode_b = 0o0777; + + at.write(a, "a"); + at.write(b, "b"); + at.set_mode(a, mode_a); + at.set_mode(b, mode_b); + + let mode_a = at.metadata(a).mode(); + let mode_b = at.metadata(b).mode(); + + // --attributes-only doesn't do anything without other attribute preservation flags + ucmd.arg("--attributes-only") + .arg(a) + .arg(b) + .succeeds() + .no_output(); + + assert_eq!("a", at.read(a)); + assert_eq!("b", at.read(b)); + assert_eq!(mode_a, at.metadata(a).mode()); + assert_eq!(mode_b, at.metadata(b).mode()); +} From 11f56a79af721fff2aab6f1472f6bf8297d5b605 Mon Sep 17 00:00:00 2001 From: Konstantin Belousov Date: Fri, 27 Oct 2023 06:53:49 +0300 Subject: [PATCH 0281/2851] freebsd: fix the 'df' command df, and perhaps other commands, get the list of the mounted filesystems with the call to getmntinfo(3). Since Rust still use FreeBSD 11.x ABI for filesystem metadata call, it should use matching versioned symbol for getmntinfo from libc. --- src/uucore/src/lib/features/fsext.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/uucore/src/lib/features/fsext.rs b/src/uucore/src/lib/features/fsext.rs index 52c079e2ed9..65f5a13b9ce 100644 --- a/src/uucore/src/lib/features/fsext.rs +++ b/src/uucore/src/lib/features/fsext.rs @@ -362,13 +362,19 @@ extern "C" { fn get_mount_info(mount_buffer_p: *mut *mut StatFs, flags: c_int) -> c_int; #[cfg(any( - target_os = "freebsd", target_os = "netbsd", target_os = "openbsd", all(target_vendor = "apple", target_arch = "aarch64") ))] #[link_name = "getmntinfo"] // spell-checker:disable-line fn get_mount_info(mount_buffer_p: *mut *mut StatFs, flags: c_int) -> c_int; + + // Rust on FreeBSD uses 11.x ABI for filesystem metadata syscalls. + // Call the right version of the symbol for getmntinfo() result to + // match libc StatFS layout. + #[cfg(target_os = "freebsd")] + #[link_name = "getmntinfo@FBSD_1.0"] // spell-checker:disable-line + fn get_mount_info(mount_buffer_p: *mut *mut StatFs, flags: c_int) -> c_int; } #[cfg(any(target_os = "linux", target_os = "android"))] From 9f63ae6645c21d7674397c0ece79e3ec6810c154 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Fri, 27 Oct 2023 14:25:41 +0200 Subject: [PATCH 0282/2851] fsext: add getmntinfo to spell-checker:ignore --- src/uucore/src/lib/features/fsext.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uucore/src/lib/features/fsext.rs b/src/uucore/src/lib/features/fsext.rs index 65f5a13b9ce..5c2121d691c 100644 --- a/src/uucore/src/lib/features/fsext.rs +++ b/src/uucore/src/lib/features/fsext.rs @@ -5,7 +5,7 @@ //! Set of functions to manage file systems -// spell-checker:ignore DATETIME subsecond (arch) bitrig ; (fs) cifs smbfs +// spell-checker:ignore DATETIME getmntinfo subsecond (arch) bitrig ; (fs) cifs smbfs use time::macros::format_description; use time::UtcOffset; From 5c100dd088eee3d5f990c651fb95753a8c318ee1 Mon Sep 17 00:00:00 2001 From: Mick van Gelderen Date: Sat, 28 Oct 2023 15:04:51 +0200 Subject: [PATCH 0283/2851] mv: Fix stderr output mv file into dir and dir into file where both are files (#5464) * Add tests mv file into dir and dir into file where both are files * Fix test_mv_dir_into_file_where_both_are_files * Fix test_mv_file_into_dir_where_both_are_files * Store String in error instead of PathBuf * Implement path_ends_with_terminator for windows * Fix compilation on windows --- src/uu/mv/src/error.rs | 7 +++++++ src/uu/mv/src/mv.rs | 33 +++++++++++++++++++++++++++++++-- tests/by-util/test_mv.rs | 29 +++++++++++++++++++++++++++++ 3 files changed, 67 insertions(+), 2 deletions(-) diff --git a/src/uu/mv/src/error.rs b/src/uu/mv/src/error.rs index e891fc2ec01..f989d4e1332 100644 --- a/src/uu/mv/src/error.rs +++ b/src/uu/mv/src/error.rs @@ -10,6 +10,7 @@ use uucore::error::UError; #[derive(Debug)] pub enum MvError { NoSuchFile(String), + CannotStatNotADirectory(String), SameFile(String, String), SelfSubdirectory(String), SelfTargetSubdirectory(String, String), @@ -17,6 +18,7 @@ pub enum MvError { NonDirectoryToDirectory(String, String), NotADirectory(String), TargetNotADirectory(String), + FailedToAccessNotADirectory(String), } impl Error for MvError {} @@ -25,6 +27,7 @@ impl Display for MvError { fn fmt(&self, f: &mut Formatter) -> Result { match self { Self::NoSuchFile(s) => write!(f, "cannot stat {s}: No such file or directory"), + Self::CannotStatNotADirectory(s) => write!(f, "cannot stat {s}: Not a directory"), Self::SameFile(s, t) => write!(f, "{s} and {t} are the same file"), Self::SelfSubdirectory(s) => write!( f, @@ -42,6 +45,10 @@ impl Display for MvError { } Self::NotADirectory(t) => write!(f, "target {t}: Not a directory"), Self::TargetNotADirectory(t) => write!(f, "target directory {t}: Not a directory"), + + Self::FailedToAccessNotADirectory(t) => { + write!(f, "failed to access {t}: Not a directory") + } } } } diff --git a/src/uu/mv/src/mv.rs b/src/uu/mv/src/mv.rs index 5c52fef26d3..0ceda8e75e8 100644 --- a/src/uu/mv/src/mv.rs +++ b/src/uu/mv/src/mv.rs @@ -103,6 +103,25 @@ static OPT_VERBOSE: &str = "verbose"; static OPT_PROGRESS: &str = "progress"; static ARG_FILES: &str = "files"; +/// Returns true if the passed `path` ends with a path terminator. +#[cfg(unix)] +fn path_ends_with_terminator(path: &Path) -> bool { + use std::os::unix::prelude::OsStrExt; + path.as_os_str() + .as_bytes() + .last() + .map_or(false, |&byte| byte == b'/' || byte == b'\\') +} + +#[cfg(windows)] +fn path_ends_with_terminator(path: &Path) -> bool { + use std::os::windows::prelude::OsStrExt; + path.as_os_str() + .encode_wide() + .last() + .map_or(false, |wide| wide == b'/'.into() || wide == b'\\'.into()) +} + #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { let mut app = uu_app(); @@ -299,7 +318,11 @@ fn handle_two_paths(source: &Path, target: &Path, opts: &Options) -> UResult<()> .into()); } if source.symlink_metadata().is_err() { - return Err(MvError::NoSuchFile(source.quote().to_string()).into()); + return Err(if path_ends_with_terminator(source) { + MvError::CannotStatNotADirectory(source.quote().to_string()).into() + } else { + MvError::NoSuchFile(source.quote().to_string()).into() + }); } if (source.eq(target) @@ -316,7 +339,13 @@ fn handle_two_paths(source: &Path, target: &Path, opts: &Options) -> UResult<()> } } - if target.is_dir() { + let target_is_dir = target.is_dir(); + + if path_ends_with_terminator(target) && !target_is_dir { + return Err(MvError::FailedToAccessNotADirectory(target.quote().to_string()).into()); + } + + if target_is_dir { if opts.no_target_dir { if source.is_dir() { rename(source, target, opts, None).map_err_context(|| { diff --git a/tests/by-util/test_mv.rs b/tests/by-util/test_mv.rs index e8866732014..c54d24ea906 100644 --- a/tests/by-util/test_mv.rs +++ b/tests/by-util/test_mv.rs @@ -1414,6 +1414,35 @@ fn test_mv_directory_into_subdirectory_of_itself_fails() { "mv: cannot move 'mydir/' to a subdirectory of itself, 'mydir/mydir_2/mydir/'", ); } + +#[test] +fn test_mv_file_into_dir_where_both_are_files() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + at.touch("a"); + at.touch("b"); + scene + .ucmd() + .arg("a") + .arg("b/") + .fails() + .stderr_contains("mv: failed to access 'b/': Not a directory"); +} + +#[test] +fn test_mv_dir_into_file_where_both_are_files() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + at.touch("a"); + at.touch("b"); + scene + .ucmd() + .arg("a/") + .arg("b") + .fails() + .stderr_contains("mv: cannot stat 'a/': Not a directory"); +} + // Todo: // $ at.touch a b From e887944ef1eab809d262dd5e8896f558f24a60ff Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Sat, 28 Oct 2023 16:53:09 +0200 Subject: [PATCH 0284/2851] Remove "last synced with" comments --- src/uu/cat/src/cat.rs | 2 -- src/uu/env/src/env.rs | 2 -- src/uu/logname/src/logname.rs | 2 -- src/uu/printenv/src/printenv.rs | 2 -- src/uu/uname/src/uname.rs | 2 -- src/uu/unlink/src/unlink.rs | 2 -- src/uu/whoami/src/whoami.rs | 2 -- src/uu/yes/src/yes.rs | 2 -- 8 files changed, 16 deletions(-) diff --git a/src/uu/cat/src/cat.rs b/src/uu/cat/src/cat.rs index d49f4aa0707..10e5d9ce1f6 100644 --- a/src/uu/cat/src/cat.rs +++ b/src/uu/cat/src/cat.rs @@ -4,8 +4,6 @@ // file that was distributed with this source code. // spell-checker:ignore (ToDO) nonprint nonblank nonprinting - -// last synced with: cat (GNU coreutils) 8.13 use clap::{crate_version, Arg, ArgAction, Command}; use std::fs::{metadata, File}; use std::io::{self, IsTerminal, Read, Write}; diff --git a/src/uu/env/src/env.rs b/src/uu/env/src/env.rs index d7c9687de60..608357f5050 100644 --- a/src/uu/env/src/env.rs +++ b/src/uu/env/src/env.rs @@ -3,8 +3,6 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -/* last synced with: env (GNU coreutils) 8.13 */ - // spell-checker:ignore (ToDO) chdir execvp progname subcommand subcommands unsets setenv putenv spawnp SIGSEGV SIGBUS sigaction use clap::{crate_name, crate_version, Arg, ArgAction, Command}; diff --git a/src/uu/logname/src/logname.rs b/src/uu/logname/src/logname.rs index 52505d98d49..55d4fec75ee 100644 --- a/src/uu/logname/src/logname.rs +++ b/src/uu/logname/src/logname.rs @@ -3,8 +3,6 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -/* last synced with: logname (GNU coreutils) 8.22 */ - // spell-checker:ignore (ToDO) getlogin userlogin use clap::{crate_version, Command}; diff --git a/src/uu/printenv/src/printenv.rs b/src/uu/printenv/src/printenv.rs index cab24336f60..47bd7c259b6 100644 --- a/src/uu/printenv/src/printenv.rs +++ b/src/uu/printenv/src/printenv.rs @@ -3,8 +3,6 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -/* last synced with: printenv (GNU coreutils) 8.13 */ - use clap::{crate_version, Arg, ArgAction, Command}; use std::env; use uucore::{error::UResult, format_usage, help_about, help_usage}; diff --git a/src/uu/uname/src/uname.rs b/src/uu/uname/src/uname.rs index 73ab07a6341..e6d5c3a0a32 100644 --- a/src/uu/uname/src/uname.rs +++ b/src/uu/uname/src/uname.rs @@ -3,8 +3,6 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// last synced with: uname (GNU coreutils) 8.21 - // spell-checker:ignore (API) nodename osname sysname (options) mnrsv mnrsvo use clap::{crate_version, Arg, ArgAction, Command}; diff --git a/src/uu/unlink/src/unlink.rs b/src/uu/unlink/src/unlink.rs index 85e1ab4f594..4c9f2d82940 100644 --- a/src/uu/unlink/src/unlink.rs +++ b/src/uu/unlink/src/unlink.rs @@ -3,8 +3,6 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -/* last synced with: unlink (GNU coreutils) 8.21 */ - use std::ffi::OsString; use std::fs::remove_file; use std::path::Path; diff --git a/src/uu/whoami/src/whoami.rs b/src/uu/whoami/src/whoami.rs index 738f7509a4d..294c9132841 100644 --- a/src/uu/whoami/src/whoami.rs +++ b/src/uu/whoami/src/whoami.rs @@ -3,8 +3,6 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -/* last synced with: whoami (GNU coreutils) 8.21 */ - use std::ffi::OsString; use clap::{crate_version, Command}; diff --git a/src/uu/yes/src/yes.rs b/src/uu/yes/src/yes.rs index a58b734045b..b1d8f9f491d 100644 --- a/src/uu/yes/src/yes.rs +++ b/src/uu/yes/src/yes.rs @@ -3,8 +3,6 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -/* last synced with: yes (GNU coreutils) 8.13 */ - // cSpell:ignore strs use clap::{builder::ValueParser, crate_version, Arg, ArgAction, Command}; From 69b7095eac172846ba62a511b096735ecb6b391e Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Sat, 28 Oct 2023 17:34:04 +0200 Subject: [PATCH 0285/2851] printf rewrite: fix compilation --- src/uu/dd/src/progress.rs | 6 ++- src/uu/printf/src/printf.rs | 8 ++-- src/uu/seq/src/seq.rs | 11 +++-- src/uucore/src/lib/features/format/mod.rs | 55 ++++++++++++++++++---- src/uucore/src/lib/features/format/spec.rs | 38 +++++++-------- 5 files changed, 80 insertions(+), 38 deletions(-) diff --git a/src/uu/dd/src/progress.rs b/src/uu/dd/src/progress.rs index f2472600927..1d9b7247d20 100644 --- a/src/uu/dd/src/progress.rs +++ b/src/uu/dd/src/progress.rs @@ -13,8 +13,8 @@ use std::io::Write; use std::sync::mpsc; use std::time::Duration; -use uucore::error::UResult; use uucore::format::sprintf; +use uucore::{error::UResult, format::FormatArgument}; use crate::numbers::{to_magnitude_and_suffix, SuffixType}; @@ -152,7 +152,9 @@ impl ProgUpdate { let (carriage_return, newline) = if rewrite { ("\r", "") } else { ("", "\n") }; // The duration should be formatted as in `printf %g`. - let duration_str = sprintf("%g", &[duration.to_string()])?; + // TODO: remove unwrap and make FormatError implement UError + let duration_str = sprintf("%g", &[FormatArgument::Float(duration)])?; + let duration_str = std::str::from_utf8(&duration_str).unwrap(); // If the number of bytes written is sufficiently large, then // print a more concise representation of the number, like diff --git a/src/uu/printf/src/printf.rs b/src/uu/printf/src/printf.rs index 36b4c34535b..6e270ec2645 100644 --- a/src/uu/printf/src/printf.rs +++ b/src/uu/printf/src/printf.rs @@ -8,7 +8,7 @@ use clap::{crate_version, Arg, ArgAction, Command}; use uucore::error::{UResult, UUsageError}; -use uucore::format::printf; +use uucore::format::{printf, FormatArgument}; use uucore::{format_usage, help_about, help_section, help_usage}; const VERSION: &str = "version"; @@ -30,12 +30,12 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let format_string = matches .get_one::(options::FORMATSTRING) .ok_or_else(|| UUsageError::new(1, "missing operand"))?; - let values: Vec = match matches.get_many::(options::ARGUMENT) { - Some(s) => s.map(|s| s.to_string()).collect(), + let values: Vec<_> = match matches.get_many::(options::ARGUMENT) { + Some(s) => s.map(|s| FormatArgument::Unparsed(s.to_string())).collect(), None => vec![], }; - printf(format_string, &values[..])?; + printf(format_string, &values)?; Ok(()) } diff --git a/src/uu/seq/src/seq.rs b/src/uu/seq/src/seq.rs index f93ced9264d..217e9042833 100644 --- a/src/uu/seq/src/seq.rs +++ b/src/uu/seq/src/seq.rs @@ -9,7 +9,7 @@ use clap::{crate_version, Arg, ArgAction, Command}; use num_traits::Zero; use uucore::error::UResult; -use uucore::format::printf; +use uucore::format::{printf, FormatArgument}; use uucore::{format_usage, help_about, help_usage}; mod error; @@ -144,8 +144,9 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { }; match result { Ok(_) => Ok(()), - Err(err) if err.kind() == ErrorKind::BrokenPipe => Ok(()), - Err(e) => Err(e.map_err_context(|| "write error".into())), + _ => todo!(), + // Err(err) if err.kind() == ErrorKind::BrokenPipe => Ok(()), + // Err(e) => Err(e.map_err_context(|| "write error".into())), } } @@ -270,7 +271,7 @@ fn print_seq( match format { Some(f) => { let s = format!("{value}"); - printf(f, &[s])?; + printf(f, &[FormatArgument::String(s)])?; } None => write_value_float(&mut stdout, &value, padding, largest_dec)?, } @@ -326,7 +327,7 @@ fn print_seq_integers( match format { Some(f) => { let s = format!("{value}"); - printf(f, &[s])?; + printf(f, &[FormatArgument::String(s)])?; } None => write_value_int(&mut stdout, &value, padding, pad)?, } diff --git a/src/uucore/src/lib/features/format/mod.rs b/src/uucore/src/lib/features/format/mod.rs index abd92011cc4..ebb1cc360eb 100644 --- a/src/uucore/src/lib/features/format/mod.rs +++ b/src/uucore/src/lib/features/format/mod.rs @@ -3,7 +3,7 @@ //! The [`printf`] and [`sprintf`] closely match the behavior of the //! corresponding C functions: the former renders a formatted string //! to stdout, the latter renders to a new [`String`] object. -//! +//! //! In addition to the [`printf`] and [`sprintf`] functions, we expose the //! [`Format`] struct, which represents a parsed format string. This reduces //! the need for parsing a format string multiple times and assures that no @@ -14,8 +14,15 @@ mod spec; use spec::Spec; -use std::io::{stdout, Write}; +use std::{ + error::Error, + fmt::Display, + io::{stdout, Write}, +}; + +use crate::error::UError; +#[derive(Debug)] pub enum FormatError { SpecError, IoError(std::io::Error), @@ -23,6 +30,21 @@ pub enum FormatError { InvalidArgument(FormatArgument), } +impl Error for FormatError {} +impl UError for FormatError {} + +impl Display for FormatError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + // TODO: Be more precise about these + match self { + FormatError::SpecError => write!(f, "invalid spec"), + FormatError::IoError(_) => write!(f, "io error"), + FormatError::NoMoreArguments => write!(f, "no more arguments"), + FormatError::InvalidArgument(_) => write!(f, "invalid argument"), + } + } +} + /// A single item to format enum FormatItem { /// A format specifier @@ -30,21 +52,28 @@ enum FormatItem { /// Some plain text Text(Vec), /// A single character - /// + /// /// Added in addition to `Text` as an optimization. Char(u8), } +#[derive(Clone, Debug)] pub enum FormatArgument { Char(char), String(String), UnsignedInt(u64), SignedInt(i64), Float(f64), + // Special argument that gets coerced into the other variants + Unparsed(String), } impl FormatItem { - fn write<'a>(&self, mut writer: impl Write, args: &mut impl Iterator) -> Result<(), FormatError> { + fn write<'a>( + &self, + mut writer: impl Write, + args: &mut impl Iterator, + ) -> Result<(), FormatError> { match self { FormatItem::Spec(spec) => spec.write(writer, args), FormatItem::Text(bytes) => writer.write_all(bytes).map_err(FormatError::IoError), @@ -110,13 +139,20 @@ fn parse_iter(fmt: &[u8]) -> impl Iterator) -> Result<(), FormatError> { +pub fn printf<'a>( + format_string: impl AsRef<[u8]>, + arguments: impl IntoIterator, +) -> Result<(), FormatError> { printf_writer(stdout(), format_string, arguments) } -fn printf_writer(mut writer: impl Write, format_string: &[u8], args: impl IntoIterator) -> Result<(), FormatError> { +fn printf_writer<'a>( + mut writer: impl Write, + format_string: impl AsRef<[u8]>, + args: impl IntoIterator, +) -> Result<(), FormatError> { let mut args = args.into_iter(); - for item in parse_iter(format_string) { + for item in parse_iter(format_string.as_ref()) { item?.write(&mut writer, &mut args)?; } Ok(()) @@ -137,7 +173,10 @@ fn printf_writer(mut writer: impl Write, format_string: &[u8], args: impl IntoIt /// let s = sprintf("hello %s", &["world".to_string()]).unwrap(); /// assert_eq!(s, "hello world".to_string()); /// ``` -pub fn sprintf(format_string: &[u8], arguments: impl IntoIterator) -> Result, FormatError> { +pub fn sprintf<'a>( + format_string: impl AsRef<[u8]>, + arguments: impl IntoIterator, +) -> Result, FormatError> { let mut writer = Vec::new(); printf_writer(&mut writer, format_string, arguments)?; Ok(writer) diff --git a/src/uucore/src/lib/features/format/spec.rs b/src/uucore/src/lib/features/format/spec.rs index d1786c3d36f..c1eb7856db1 100644 --- a/src/uucore/src/lib/features/format/spec.rs +++ b/src/uucore/src/lib/features/format/spec.rs @@ -257,7 +257,7 @@ impl Spec { pub fn write<'a>( &self, mut writer: impl Write, - mut args: impl Iterator, + mut args: impl Iterator, ) -> Result<(), FormatError> { match self { &Spec::Char { width, align_left } => { @@ -265,7 +265,7 @@ impl Spec { let arg = next_arg(&mut args)?; match arg { FormatArgument::Char(c) => write_padded(writer, c, width, false, align_left), - _ => Err(FormatError::InvalidArgument(arg)), + _ => Err(FormatError::InvalidArgument(arg.clone())), } } &Spec::String { width, align_left } => { @@ -273,7 +273,7 @@ impl Spec { let arg = next_arg(&mut args)?; match arg { FormatArgument::String(s) => write_padded(writer, s, width, false, align_left), - _ => Err(FormatError::InvalidArgument(arg)), + _ => Err(FormatError::InvalidArgument(arg.clone())), } } &Spec::SignedInt { @@ -285,10 +285,10 @@ impl Spec { let arg = next_arg(&mut args)?; let FormatArgument::SignedInt(i) = arg else { - return Err(FormatError::InvalidArgument(arg)); + return Err(FormatError::InvalidArgument(arg.clone())); }; - if i >= 0 { + if *i >= 0 { match positive_sign { PositiveSign::None => Ok(()), PositiveSign::Plus => write!(writer, "+"), @@ -313,7 +313,7 @@ impl Spec { let arg = next_arg(args)?; let FormatArgument::SignedInt(i) = arg else { - return Err(FormatError::InvalidArgument(arg)); + return Err(FormatError::InvalidArgument(arg.clone())); }; let s = match variant { @@ -355,7 +355,7 @@ impl Spec { let arg = next_arg(args)?; let FormatArgument::Float(f) = arg else { - return Err(FormatError::InvalidArgument(arg)); + return Err(FormatError::InvalidArgument(arg.clone())); }; if f.is_sign_positive() { @@ -369,16 +369,16 @@ impl Spec { let s = match variant { FloatVariant::Decimal => { - format_float_decimal(f, precision, case, force_decimal) + format_float_decimal(*f, precision, case, force_decimal) } FloatVariant::Scientific => { - format_float_scientific(f, precision, case, force_decimal) + format_float_scientific(*f, precision, case, force_decimal) } FloatVariant::Shortest => { - format_float_shortest(f, precision, case, force_decimal) + format_float_shortest(*f, precision, case, force_decimal) } FloatVariant::Hexadecimal => { - format_float_hexadecimal(f, precision, case, force_decimal) + format_float_hexadecimal(*f, precision, case, force_decimal) } }; @@ -500,29 +500,29 @@ fn format_float_hexadecimal( return s; } -fn resolve_asterisk( +fn resolve_asterisk<'a>( option: Option>, - args: impl Iterator, + args: impl Iterator, ) -> Result, FormatError> { Ok(match option { None => None, Some(CanAsterisk::Asterisk) => { let arg = next_arg(args)?; match arg { - FormatArgument::UnsignedInt(u) => match usize::try_from(u) { + FormatArgument::UnsignedInt(u) => match usize::try_from(*u) { Ok(u) => Some(u), - Err(_) => return Err(FormatError::InvalidArgument(arg)), + Err(_) => return Err(FormatError::InvalidArgument(arg.clone())), }, - _ => return Err(FormatError::InvalidArgument(arg)), + _ => return Err(FormatError::InvalidArgument(arg.clone())), } } Some(CanAsterisk::Fixed(w)) => Some(w), }) } -fn next_arg( - mut arguments: impl Iterator, -) -> Result { +fn next_arg<'a>( + mut arguments: impl Iterator, +) -> Result<&'a FormatArgument, FormatError> { arguments.next().ok_or(FormatError::NoMoreArguments) } From f117fc1bab8aaab4c26d41bad40c111904f5f9b6 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Sat, 28 Oct 2023 17:34:04 +0200 Subject: [PATCH 0286/2851] printf rewrite: fix compilation --- src/uu/dd/src/progress.rs | 6 ++- src/uu/printf/src/printf.rs | 8 ++-- src/uu/seq/src/seq.rs | 11 +++-- src/uucore/src/lib/features.rs | 8 +--- src/uucore/src/lib/features/format/mod.rs | 55 ++++++++++++++++++---- src/uucore/src/lib/features/format/spec.rs | 40 ++++++++-------- src/uucore/src/lib/lib.rs | 6 +-- 7 files changed, 85 insertions(+), 49 deletions(-) diff --git a/src/uu/dd/src/progress.rs b/src/uu/dd/src/progress.rs index f2472600927..1d9b7247d20 100644 --- a/src/uu/dd/src/progress.rs +++ b/src/uu/dd/src/progress.rs @@ -13,8 +13,8 @@ use std::io::Write; use std::sync::mpsc; use std::time::Duration; -use uucore::error::UResult; use uucore::format::sprintf; +use uucore::{error::UResult, format::FormatArgument}; use crate::numbers::{to_magnitude_and_suffix, SuffixType}; @@ -152,7 +152,9 @@ impl ProgUpdate { let (carriage_return, newline) = if rewrite { ("\r", "") } else { ("", "\n") }; // The duration should be formatted as in `printf %g`. - let duration_str = sprintf("%g", &[duration.to_string()])?; + // TODO: remove unwrap and make FormatError implement UError + let duration_str = sprintf("%g", &[FormatArgument::Float(duration)])?; + let duration_str = std::str::from_utf8(&duration_str).unwrap(); // If the number of bytes written is sufficiently large, then // print a more concise representation of the number, like diff --git a/src/uu/printf/src/printf.rs b/src/uu/printf/src/printf.rs index 36b4c34535b..6e270ec2645 100644 --- a/src/uu/printf/src/printf.rs +++ b/src/uu/printf/src/printf.rs @@ -8,7 +8,7 @@ use clap::{crate_version, Arg, ArgAction, Command}; use uucore::error::{UResult, UUsageError}; -use uucore::format::printf; +use uucore::format::{printf, FormatArgument}; use uucore::{format_usage, help_about, help_section, help_usage}; const VERSION: &str = "version"; @@ -30,12 +30,12 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let format_string = matches .get_one::(options::FORMATSTRING) .ok_or_else(|| UUsageError::new(1, "missing operand"))?; - let values: Vec = match matches.get_many::(options::ARGUMENT) { - Some(s) => s.map(|s| s.to_string()).collect(), + let values: Vec<_> = match matches.get_many::(options::ARGUMENT) { + Some(s) => s.map(|s| FormatArgument::Unparsed(s.to_string())).collect(), None => vec![], }; - printf(format_string, &values[..])?; + printf(format_string, &values)?; Ok(()) } diff --git a/src/uu/seq/src/seq.rs b/src/uu/seq/src/seq.rs index f93ced9264d..217e9042833 100644 --- a/src/uu/seq/src/seq.rs +++ b/src/uu/seq/src/seq.rs @@ -9,7 +9,7 @@ use clap::{crate_version, Arg, ArgAction, Command}; use num_traits::Zero; use uucore::error::UResult; -use uucore::format::printf; +use uucore::format::{printf, FormatArgument}; use uucore::{format_usage, help_about, help_usage}; mod error; @@ -144,8 +144,9 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { }; match result { Ok(_) => Ok(()), - Err(err) if err.kind() == ErrorKind::BrokenPipe => Ok(()), - Err(e) => Err(e.map_err_context(|| "write error".into())), + _ => todo!(), + // Err(err) if err.kind() == ErrorKind::BrokenPipe => Ok(()), + // Err(e) => Err(e.map_err_context(|| "write error".into())), } } @@ -270,7 +271,7 @@ fn print_seq( match format { Some(f) => { let s = format!("{value}"); - printf(f, &[s])?; + printf(f, &[FormatArgument::String(s)])?; } None => write_value_float(&mut stdout, &value, padding, largest_dec)?, } @@ -326,7 +327,7 @@ fn print_seq_integers( match format { Some(f) => { let s = format!("{value}"); - printf(f, &[s])?; + printf(f, &[FormatArgument::String(s)])?; } None => write_value_int(&mut stdout, &value, padding, pad)?, } diff --git a/src/uucore/src/lib/features.rs b/src/uucore/src/lib/features.rs index 133050954dd..1d0d437824d 100644 --- a/src/uucore/src/lib/features.rs +++ b/src/uucore/src/lib/features.rs @@ -8,16 +8,14 @@ pub mod backup_control; #[cfg(feature = "encoding")] pub mod encoding; +#[cfg(feature = "format")] +pub mod format; #[cfg(feature = "fs")] pub mod fs; #[cfg(feature = "fsext")] pub mod fsext; #[cfg(feature = "lines")] pub mod lines; -#[cfg(feature = "format")] -pub mod format; -#[cfg(feature = "memo")] -pub mod memo; #[cfg(feature = "quoting-style")] pub mod quoting_style; #[cfg(feature = "ranges")] @@ -26,8 +24,6 @@ pub mod ranges; pub mod ringbuffer; #[cfg(feature = "sum")] pub mod sum; -#[cfg(feature = "memo")] -mod tokenize; #[cfg(feature = "update-control")] pub mod update_control; #[cfg(feature = "version-cmp")] diff --git a/src/uucore/src/lib/features/format/mod.rs b/src/uucore/src/lib/features/format/mod.rs index abd92011cc4..ebb1cc360eb 100644 --- a/src/uucore/src/lib/features/format/mod.rs +++ b/src/uucore/src/lib/features/format/mod.rs @@ -3,7 +3,7 @@ //! The [`printf`] and [`sprintf`] closely match the behavior of the //! corresponding C functions: the former renders a formatted string //! to stdout, the latter renders to a new [`String`] object. -//! +//! //! In addition to the [`printf`] and [`sprintf`] functions, we expose the //! [`Format`] struct, which represents a parsed format string. This reduces //! the need for parsing a format string multiple times and assures that no @@ -14,8 +14,15 @@ mod spec; use spec::Spec; -use std::io::{stdout, Write}; +use std::{ + error::Error, + fmt::Display, + io::{stdout, Write}, +}; + +use crate::error::UError; +#[derive(Debug)] pub enum FormatError { SpecError, IoError(std::io::Error), @@ -23,6 +30,21 @@ pub enum FormatError { InvalidArgument(FormatArgument), } +impl Error for FormatError {} +impl UError for FormatError {} + +impl Display for FormatError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + // TODO: Be more precise about these + match self { + FormatError::SpecError => write!(f, "invalid spec"), + FormatError::IoError(_) => write!(f, "io error"), + FormatError::NoMoreArguments => write!(f, "no more arguments"), + FormatError::InvalidArgument(_) => write!(f, "invalid argument"), + } + } +} + /// A single item to format enum FormatItem { /// A format specifier @@ -30,21 +52,28 @@ enum FormatItem { /// Some plain text Text(Vec), /// A single character - /// + /// /// Added in addition to `Text` as an optimization. Char(u8), } +#[derive(Clone, Debug)] pub enum FormatArgument { Char(char), String(String), UnsignedInt(u64), SignedInt(i64), Float(f64), + // Special argument that gets coerced into the other variants + Unparsed(String), } impl FormatItem { - fn write<'a>(&self, mut writer: impl Write, args: &mut impl Iterator) -> Result<(), FormatError> { + fn write<'a>( + &self, + mut writer: impl Write, + args: &mut impl Iterator, + ) -> Result<(), FormatError> { match self { FormatItem::Spec(spec) => spec.write(writer, args), FormatItem::Text(bytes) => writer.write_all(bytes).map_err(FormatError::IoError), @@ -110,13 +139,20 @@ fn parse_iter(fmt: &[u8]) -> impl Iterator) -> Result<(), FormatError> { +pub fn printf<'a>( + format_string: impl AsRef<[u8]>, + arguments: impl IntoIterator, +) -> Result<(), FormatError> { printf_writer(stdout(), format_string, arguments) } -fn printf_writer(mut writer: impl Write, format_string: &[u8], args: impl IntoIterator) -> Result<(), FormatError> { +fn printf_writer<'a>( + mut writer: impl Write, + format_string: impl AsRef<[u8]>, + args: impl IntoIterator, +) -> Result<(), FormatError> { let mut args = args.into_iter(); - for item in parse_iter(format_string) { + for item in parse_iter(format_string.as_ref()) { item?.write(&mut writer, &mut args)?; } Ok(()) @@ -137,7 +173,10 @@ fn printf_writer(mut writer: impl Write, format_string: &[u8], args: impl IntoIt /// let s = sprintf("hello %s", &["world".to_string()]).unwrap(); /// assert_eq!(s, "hello world".to_string()); /// ``` -pub fn sprintf(format_string: &[u8], arguments: impl IntoIterator) -> Result, FormatError> { +pub fn sprintf<'a>( + format_string: impl AsRef<[u8]>, + arguments: impl IntoIterator, +) -> Result, FormatError> { let mut writer = Vec::new(); printf_writer(&mut writer, format_string, arguments)?; Ok(writer) diff --git a/src/uucore/src/lib/features/format/spec.rs b/src/uucore/src/lib/features/format/spec.rs index d1786c3d36f..e66cad32d65 100644 --- a/src/uucore/src/lib/features/format/spec.rs +++ b/src/uucore/src/lib/features/format/spec.rs @@ -257,7 +257,7 @@ impl Spec { pub fn write<'a>( &self, mut writer: impl Write, - mut args: impl Iterator, + mut args: impl Iterator, ) -> Result<(), FormatError> { match self { &Spec::Char { width, align_left } => { @@ -265,7 +265,7 @@ impl Spec { let arg = next_arg(&mut args)?; match arg { FormatArgument::Char(c) => write_padded(writer, c, width, false, align_left), - _ => Err(FormatError::InvalidArgument(arg)), + _ => Err(FormatError::InvalidArgument(arg.clone())), } } &Spec::String { width, align_left } => { @@ -273,7 +273,7 @@ impl Spec { let arg = next_arg(&mut args)?; match arg { FormatArgument::String(s) => write_padded(writer, s, width, false, align_left), - _ => Err(FormatError::InvalidArgument(arg)), + _ => Err(FormatError::InvalidArgument(arg.clone())), } } &Spec::SignedInt { @@ -285,10 +285,10 @@ impl Spec { let arg = next_arg(&mut args)?; let FormatArgument::SignedInt(i) = arg else { - return Err(FormatError::InvalidArgument(arg)); + return Err(FormatError::InvalidArgument(arg.clone())); }; - if i >= 0 { + if *i >= 0 { match positive_sign { PositiveSign::None => Ok(()), PositiveSign::Plus => write!(writer, "+"), @@ -313,7 +313,7 @@ impl Spec { let arg = next_arg(args)?; let FormatArgument::SignedInt(i) = arg else { - return Err(FormatError::InvalidArgument(arg)); + return Err(FormatError::InvalidArgument(arg.clone())); }; let s = match variant { @@ -355,7 +355,7 @@ impl Spec { let arg = next_arg(args)?; let FormatArgument::Float(f) = arg else { - return Err(FormatError::InvalidArgument(arg)); + return Err(FormatError::InvalidArgument(arg.clone())); }; if f.is_sign_positive() { @@ -369,16 +369,16 @@ impl Spec { let s = match variant { FloatVariant::Decimal => { - format_float_decimal(f, precision, case, force_decimal) + format_float_decimal(*f, precision, case, force_decimal) } FloatVariant::Scientific => { - format_float_scientific(f, precision, case, force_decimal) + format_float_scientific(*f, precision, case, force_decimal) } FloatVariant::Shortest => { - format_float_shortest(f, precision, case, force_decimal) + format_float_shortest(*f, precision, case, force_decimal) } FloatVariant::Hexadecimal => { - format_float_hexadecimal(f, precision, case, force_decimal) + format_float_hexadecimal(*f, precision, case, force_decimal) } }; @@ -490,7 +490,7 @@ fn format_float_hexadecimal( let mut s = match (precision, force_decimal) { (0, ForceDecimal::No) => format!("0x{first_digit}p{exponent:+x}"), (0, ForceDecimal::Yes) => format!("0x{first_digit}.p{exponent:+x}"), - _ => format!("0x{first_digit}.{mantissa:0>13x}p{exponent:+x}") + _ => format!("0x{first_digit}.{mantissa:0>13x}p{exponent:+x}"), }; if case == Case::Uppercase { @@ -500,29 +500,29 @@ fn format_float_hexadecimal( return s; } -fn resolve_asterisk( +fn resolve_asterisk<'a>( option: Option>, - args: impl Iterator, + args: impl Iterator, ) -> Result, FormatError> { Ok(match option { None => None, Some(CanAsterisk::Asterisk) => { let arg = next_arg(args)?; match arg { - FormatArgument::UnsignedInt(u) => match usize::try_from(u) { + FormatArgument::UnsignedInt(u) => match usize::try_from(*u) { Ok(u) => Some(u), - Err(_) => return Err(FormatError::InvalidArgument(arg)), + Err(_) => return Err(FormatError::InvalidArgument(arg.clone())), }, - _ => return Err(FormatError::InvalidArgument(arg)), + _ => return Err(FormatError::InvalidArgument(arg.clone())), } } Some(CanAsterisk::Fixed(w)) => Some(w), }) } -fn next_arg( - mut arguments: impl Iterator, -) -> Result { +fn next_arg<'a>( + mut arguments: impl Iterator, +) -> Result<&'a FormatArgument, FormatError> { arguments.next().ok_or(FormatError::NoMoreArguments) } diff --git a/src/uucore/src/lib/lib.rs b/src/uucore/src/lib/lib.rs index 0540275eee4..af8668ef02f 100644 --- a/src/uucore/src/lib/lib.rs +++ b/src/uucore/src/lib/lib.rs @@ -37,16 +37,14 @@ pub use crate::parser::shortcut_value_parser; pub use crate::features::backup_control; #[cfg(feature = "encoding")] pub use crate::features::encoding; +#[cfg(feature = "format")] +pub use crate::features::format; #[cfg(feature = "fs")] pub use crate::features::fs; #[cfg(feature = "fsext")] pub use crate::features::fsext; #[cfg(feature = "lines")] pub use crate::features::lines; -#[cfg(feature = "format")] -pub use crate::features::format; -#[cfg(feature = "memo")] -pub use crate::features::memo; #[cfg(feature = "quoting-style")] pub use crate::features::quoting_style; #[cfg(feature = "ranges")] From c24a51403a8e48af7fc3417a46bd6795f28bcedc Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sat, 28 Oct 2023 22:29:24 +0200 Subject: [PATCH 0287/2851] cat: return the same error message as GNU with loop symlink (#5466) * cat: return the same error message as GNU with loop symlink Should fix tests/du/long-sloop.sh because it is using cat as a ref for error messages Co-authored-by: Daniel Hofstetter --- src/uu/cat/src/cat.rs | 22 ++++++++++++++++++++-- tests/by-util/test_cat.rs | 12 ++++++++++++ 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/src/uu/cat/src/cat.rs b/src/uu/cat/src/cat.rs index 10e5d9ce1f6..da47485cc17 100644 --- a/src/uu/cat/src/cat.rs +++ b/src/uu/cat/src/cat.rs @@ -3,7 +3,7 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// spell-checker:ignore (ToDO) nonprint nonblank nonprinting +// spell-checker:ignore (ToDO) nonprint nonblank nonprinting ELOOP use clap::{crate_version, Arg, ArgAction, Command}; use std::fs::{metadata, File}; use std::io::{self, IsTerminal, Read, Write}; @@ -50,6 +50,8 @@ enum CatError { IsDirectory, #[error("input file is output file")] OutputIsInput, + #[error("Too many levels of symbolic links")] + TooManySymlinks, } type CatResult = Result; @@ -401,7 +403,23 @@ fn get_input_type(path: &str) -> CatResult { return Ok(InputType::StdIn); } - let ft = metadata(path)?.file_type(); + let ft = match metadata(path) { + Ok(md) => md.file_type(), + Err(e) => { + if let Some(raw_error) = e.raw_os_error() { + // On Unix-like systems, the error code for "Too many levels of symbolic links" is 40 (ELOOP). + // we want to provide a proper error message in this case. + #[cfg(not(target_os = "macos"))] + let too_many_symlink_code = 40; + #[cfg(target_os = "macos")] + let too_many_symlink_code = 62; + if raw_error == too_many_symlink_code { + return Err(CatError::TooManySymlinks); + } + } + return Err(CatError::Io(e)); + } + }; match ft { #[cfg(unix)] ft if ft.is_block_device() => Ok(InputType::BlockDevice), diff --git a/tests/by-util/test_cat.rs b/tests/by-util/test_cat.rs index 27f40356de5..aa86ab06652 100644 --- a/tests/by-util/test_cat.rs +++ b/tests/by-util/test_cat.rs @@ -540,3 +540,15 @@ fn test_write_to_self() { "first_file_content.second_file_content." ); } + +#[test] +#[cfg(unix)] +fn test_error_loop() { + let (at, mut ucmd) = at_and_ucmd!(); + at.symlink_file("2", "1"); + at.symlink_file("3", "2"); + at.symlink_file("1", "3"); + ucmd.arg("1") + .fails() + .stderr_is("cat: 1: Too many levels of symbolic links\n"); +} From 6a468e928d3ff0bb294b0e59d2afe300e4ce19c0 Mon Sep 17 00:00:00 2001 From: Zhuoxun Yang Date: Sun, 29 Oct 2023 18:54:40 +0800 Subject: [PATCH 0288/2851] fuzz: use parse_size_u64 --- fuzz/fuzz_targets/fuzz_parse_size.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fuzz/fuzz_targets/fuzz_parse_size.rs b/fuzz/fuzz_targets/fuzz_parse_size.rs index 23b3b5ea426..d032adf0666 100644 --- a/fuzz/fuzz_targets/fuzz_parse_size.rs +++ b/fuzz/fuzz_targets/fuzz_parse_size.rs @@ -1,10 +1,10 @@ #![no_main] use libfuzzer_sys::fuzz_target; -use uucore::parse_size::parse_size; +use uucore::parse_size::parse_size_u64; fuzz_target!(|data: &[u8]| { if let Ok(s) = std::str::from_utf8(data) { - _ = parse_size(s); + _ = parse_size_u64(s); } }); From a7cc3b6dcaee8f06cc7607e6c4e598c982b064ce Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Sun, 29 Oct 2023 14:37:47 +0100 Subject: [PATCH 0289/2851] cp: restrict two test functions to linux/mac/win --- tests/by-util/test_cp.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/by-util/test_cp.rs b/tests/by-util/test_cp.rs index c79367afb2e..07b52523acf 100644 --- a/tests/by-util/test_cp.rs +++ b/tests/by-util/test_cp.rs @@ -3216,6 +3216,7 @@ fn test_cp_archive_on_directory_ending_dot() { } #[test] +#[cfg(any(target_os = "linux", target_os = "windows", target_os = "macos"))] fn test_cp_debug_default() { let ts = TestScenario::new(util_name!()); let at = &ts.fixtures; @@ -3243,6 +3244,7 @@ fn test_cp_debug_default() { } #[test] +#[cfg(any(target_os = "linux", target_os = "windows", target_os = "macos"))] fn test_cp_debug_multiple_default() { let ts = TestScenario::new(util_name!()); let at = &ts.fixtures; From d899787ba6f45044b0e29222bc10d6f09317053e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 29 Oct 2023 17:04:13 +0000 Subject: [PATCH 0290/2851] chore(deps): update rust crate procfs to 0.16 --- Cargo.lock | 40 +++++++++++++++------------------------- Cargo.toml | 2 +- 2 files changed, 16 insertions(+), 26 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 164d6d6b7d8..048b0026362 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1207,12 +1207,6 @@ version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4" -[[package]] -name = "linux-raw-sys" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" - [[package]] name = "linux-raw-sys" version = "0.3.8" @@ -1598,15 +1592,25 @@ dependencies = [ [[package]] name = "procfs" -version = "0.15.1" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "943ca7f9f29bab5844ecd8fdb3992c5969b6622bb9609b9502fef9b4310e3f1f" +checksum = "731e0d9356b0c25f16f33b5be79b1c57b562f141ebfcdb0ad8ac2c13a24293b4" dependencies = [ - "bitflags 1.3.2", - "byteorder", + "bitflags 2.4.0", "hex", "lazy_static", - "rustix 0.36.16", + "procfs-core", + "rustix 0.38.21", +] + +[[package]] +name = "procfs-core" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d3554923a69f4ce04c4a754260c338f505ce22642d3830e049a399fc2059a29" +dependencies = [ + "bitflags 2.4.0", + "hex", ] [[package]] @@ -1822,20 +1826,6 @@ dependencies = [ "semver", ] -[[package]] -name = "rustix" -version = "0.36.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6da3636faa25820d8648e0e31c5d519bbb01f72fdf57131f0f5f7da5fed36eab" -dependencies = [ - "bitflags 1.3.2", - "errno", - "io-lifetimes", - "libc", - "linux-raw-sys 0.1.4", - "windows-sys 0.45.0", -] - [[package]] name = "rustix" version = "0.37.26" diff --git a/Cargo.toml b/Cargo.toml index afddd49850c..a565370a4e3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -492,7 +492,7 @@ hex-literal = "0.4.1" rstest = { workspace = true } [target.'cfg(any(target_os = "linux", target_os = "android"))'.dev-dependencies] -procfs = { version = "0.15", default-features = false } +procfs = { version = "0.16", default-features = false } rlimit = "0.10.1" [target.'cfg(unix)'.dev-dependencies] From f39ab620a6f421acc9a21dd6c2526efc639a2759 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Mon, 30 Oct 2023 07:14:26 +0100 Subject: [PATCH 0291/2851] cat: use error code 62 for ELOOP on FreeBSD --- src/uu/cat/src/cat.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/uu/cat/src/cat.rs b/src/uu/cat/src/cat.rs index da47485cc17..a7a4c5f406e 100644 --- a/src/uu/cat/src/cat.rs +++ b/src/uu/cat/src/cat.rs @@ -409,9 +409,9 @@ fn get_input_type(path: &str) -> CatResult { if let Some(raw_error) = e.raw_os_error() { // On Unix-like systems, the error code for "Too many levels of symbolic links" is 40 (ELOOP). // we want to provide a proper error message in this case. - #[cfg(not(target_os = "macos"))] + #[cfg(not(any(target_os = "macos", target_os = "freebsd")))] let too_many_symlink_code = 40; - #[cfg(target_os = "macos")] + #[cfg(any(target_os = "macos", target_os = "freebsd"))] let too_many_symlink_code = 62; if raw_error == too_many_symlink_code { return Err(CatError::TooManySymlinks); From 6e114fe2034f106400b835afdc49efdb5c9a6126 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Mon, 30 Oct 2023 09:26:47 +0100 Subject: [PATCH 0292/2851] deny.toml: remove two entries from skip list rustix & linux-raw-sys --- deny.toml | 3 --- 1 file changed, 3 deletions(-) diff --git a/deny.toml b/deny.toml index fa8f77c0128..03301ad7cd4 100644 --- a/deny.toml +++ b/deny.toml @@ -58,10 +58,7 @@ highlight = "all" # introduces it. # spell-checker: disable skip = [ - # procfs - { name = "rustix", version = "0.36.16" }, # rustix - { name = "linux-raw-sys", version = "0.1.4" }, { name = "linux-raw-sys", version = "0.3.8" }, # terminal_size { name = "rustix", version = "0.37.26" }, From a4775d288bc63b01142f1eeaa0d7b716c35fecc6 Mon Sep 17 00:00:00 2001 From: tommady Date: Tue, 31 Oct 2023 00:55:03 +0800 Subject: [PATCH 0293/2851] cp: fix cp -rT dir dir2 leads to different result than with GNU cp (#5467) * add a test case test_cp_treat_dest_as_a_normal_file * fix 5457 * cp: fix comment --------- Co-authored-by: Daniel Hofstetter --- src/uu/cp/src/copydir.rs | 18 +++++++++++++++--- tests/by-util/test_cp.rs | 16 ++++++++++++++++ 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/src/uu/cp/src/copydir.rs b/src/uu/cp/src/copydir.rs index a8b941364a7..763d66c0b03 100644 --- a/src/uu/cp/src/copydir.rs +++ b/src/uu/cp/src/copydir.rs @@ -87,6 +87,9 @@ struct Context<'a> { /// The target path to which the directory will be copied. target: &'a Path, + + /// The source path from which the directory will be copied. + root: &'a Path, } impl<'a> Context<'a> { @@ -102,6 +105,7 @@ impl<'a> Context<'a> { current_dir, root_parent, target, + root, }) } } @@ -156,11 +160,19 @@ struct Entry { } impl Entry { - fn new(context: &Context, direntry: &DirEntry) -> Result { + fn new( + context: &Context, + direntry: &DirEntry, + no_target_dir: bool, + ) -> Result { let source_relative = direntry.path().to_path_buf(); let source_absolute = context.current_dir.join(&source_relative); - let descendant = + let mut descendant = get_local_to_root_parent(&source_absolute, context.root_parent.as_deref())?; + if no_target_dir { + descendant = descendant.strip_prefix(context.root)?.to_path_buf(); + } + let local_to_target = context.target.join(descendant); let target_is_file = context.target.is_file(); Ok(Self { @@ -389,7 +401,7 @@ pub(crate) fn copy_directory( { match direntry_result { Ok(direntry) => { - let entry = Entry::new(&context, &direntry)?; + let entry = Entry::new(&context, &direntry, options.no_target_dir)?; copy_direntry( progress_bar, entry, diff --git a/tests/by-util/test_cp.rs b/tests/by-util/test_cp.rs index c79367afb2e..565279a6286 100644 --- a/tests/by-util/test_cp.rs +++ b/tests/by-util/test_cp.rs @@ -230,6 +230,22 @@ fn test_cp_arg_no_target_directory() { .stderr_contains("cannot overwrite directory"); } +#[test] +fn test_cp_arg_no_target_directory_with_recursive() { + let (at, mut ucmd) = at_and_ucmd!(); + + at.mkdir("dir"); + at.mkdir("dir2"); + at.touch("dir/a"); + at.touch("dir/b"); + + ucmd.arg("-rT").arg("dir").arg("dir2").succeeds(); + + assert!(at.plus("dir2").join("a").exists()); + assert!(at.plus("dir2").join("b").exists()); + assert!(!at.plus("dir2").join("dir").exists()); +} + #[test] fn test_cp_target_directory_is_file() { new_ucmd!() From 615b562b64f2d392975a697fa36748f5c2759f08 Mon Sep 17 00:00:00 2001 From: tommady Date: Tue, 31 Oct 2023 16:08:40 +0800 Subject: [PATCH 0294/2851] github action: split the run of the fuzzers (#5444) * fix-5443 by using strategy --- .github/workflows/fuzzing.yml | 68 ++++++++++++++++++++--------------- 1 file changed, 39 insertions(+), 29 deletions(-) diff --git a/.github/workflows/fuzzing.yml b/.github/workflows/fuzzing.yml index 311d6a0d7ac..e7a9cb1e329 100644 --- a/.github/workflows/fuzzing.yml +++ b/.github/workflows/fuzzing.yml @@ -13,52 +13,62 @@ concurrency: cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} jobs: - fuzz: + fuzz-build: + name: Build the fuzzers + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@nightly + - name: Install `cargo-fuzz` + run: cargo install cargo-fuzz + - uses: Swatinem/rust-cache@v2 + with: + shared-key: "cargo-fuzz-cache-key" + cache-directories: "fuzz/target" + - name: Run `cargo-fuzz build` + run: cargo +nightly fuzz build + + fuzz-run: + needs: fuzz-build name: Run the fuzzers runs-on: ubuntu-latest env: RUN_FOR: 60 + strategy: + matrix: + test-target: + [ + fuzz_date, + fuzz_test, + fuzz_expr, + fuzz_parse_glob, + fuzz_parse_size, + fuzz_parse_time, + # adding more fuzz tests here. + # e.g. fuzz_test_a, + ] steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@nightly - name: Install `cargo-fuzz` run: cargo install cargo-fuzz - uses: Swatinem/rust-cache@v2 + with: + shared-key: "cargo-fuzz-cache-key" + cache-directories: "fuzz/target" - name: Restore Cached Corpus uses: actions/cache/restore@v3 with: - key: corpus-cache + key: corpus-cache-${{ matrix.test-target }} path: | - fuzz/corpus - - name: Run fuzz_date for XX seconds - continue-on-error: true - shell: bash - run: | - cargo +nightly fuzz run fuzz_date -- -max_total_time=${{ env.RUN_FOR }} -detect_leaks=0 - - name: Run fuzz_test for XX seconds - shell: bash - run: | - cargo +nightly fuzz run fuzz_test -- -max_total_time=${{ env.RUN_FOR }} -detect_leaks=0 - - name: Run fuzz_expr for XX seconds - continue-on-error: true - shell: bash - run: | - cargo +nightly fuzz run fuzz_expr -- -max_total_time=${{ env.RUN_FOR }} -detect_leaks=0 - - name: Run fuzz_parse_glob for XX seconds - shell: bash - run: | - cargo +nightly fuzz run fuzz_parse_glob -- -max_total_time=${{ env.RUN_FOR }} -detect_leaks=0 - - name: Run fuzz_parse_size for XX seconds - shell: bash - run: | - cargo +nightly fuzz run fuzz_parse_size -- -max_total_time=${{ env.RUN_FOR }} -detect_leaks=0 - - name: Run fuzz_parse_time for XX seconds + fuzz/corpus/${{ matrix.test-target }} + - name: Run ${{ matrix.test-target }} for XX seconds shell: bash run: | - cargo +nightly fuzz run fuzz_parse_time -- -max_total_time=${{ env.RUN_FOR }} -detect_leaks=0 + cargo +nightly fuzz run ${{ matrix.test-target }} -- -max_total_time=${{ env.RUN_FOR }} -detect_leaks=0 - name: Save Corpus Cache uses: actions/cache/save@v3 with: - key: corpus-cache + key: corpus-cache-${{ matrix.test-target }} path: | - fuzz/corpus + fuzz/corpus/${{ matrix.test-target }} From a6522e011407bc098381cb7f601a69fdda15d71e Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Thu, 2 Nov 2023 16:15:18 +0100 Subject: [PATCH 0295/2851] `cp`: remove `crash!` call It seems to be unnecessary since we have already made the path relative using `construct_dest_path`. --- src/uu/cp/src/cp.rs | 38 +++++++++++++------------------------- 1 file changed, 13 insertions(+), 25 deletions(-) diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index dce35a8b9f9..7265e89f12a 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -7,7 +7,6 @@ #![allow(clippy::extra_unused_lifetimes)] use quick_error::quick_error; -use std::borrow::Cow; use std::cmp::Ordering; use std::collections::{HashMap, HashSet}; use std::env; @@ -41,8 +40,8 @@ use uucore::{backup_control, update_control}; // requires these enum. pub use uucore::{backup_control::BackupMode, update_control::UpdateMode}; use uucore::{ - crash, format_usage, help_about, help_section, help_usage, prompt_yes, show_error, - show_warning, util_name, + format_usage, help_about, help_section, help_usage, prompt_yes, show_error, show_warning, + util_name, }; use crate::copydir::copy_directory; @@ -144,6 +143,7 @@ pub enum SparseMode { } /// The expected file type of copy target +#[derive(Copy, Clone)] pub enum TargetType { Directory, File, @@ -1195,7 +1195,7 @@ pub fn copy(sources: &[PathBuf], target: &Path, options: &Options) -> CopyResult &progress_bar, source, target, - &target_type, + target_type, options, &mut symlinked_files, &mut copied_files, @@ -1220,7 +1220,7 @@ pub fn copy(sources: &[PathBuf], target: &Path, options: &Options) -> CopyResult fn construct_dest_path( source_path: &Path, target: &Path, - target_type: &TargetType, + target_type: TargetType, options: &Options, ) -> CopyResult { if options.no_target_dir && target.is_dir() { @@ -1235,7 +1235,7 @@ fn construct_dest_path( return Err("with --parents, the destination must be a directory".into()); } - Ok(match *target_type { + Ok(match target_type { TargetType::Directory => { let root = if options.parents { Path::new("") @@ -1252,7 +1252,7 @@ fn copy_source( progress_bar: &Option, source: &Path, target: &Path, - target_type: &TargetType, + target_type: TargetType, options: &Options, symlinked_files: &mut HashSet, copied_files: &mut HashMap, @@ -1995,24 +1995,12 @@ fn copy_link( ) -> CopyResult<()> { // Here, we will copy the symlink itself (actually, just recreate it) let link = fs::read_link(source)?; - let dest: Cow<'_, Path> = if dest.is_dir() { - match source.file_name() { - Some(name) => dest.join(name).into(), - None => crash!( - EXIT_ERR, - "cannot stat {}: No such file or directory", - source.quote() - ), - } - } else { - // we always need to remove the file to be able to create a symlink, - // even if it is writeable. - if dest.is_symlink() || dest.is_file() { - fs::remove_file(dest)?; - } - dest.into() - }; - symlink_file(&link, &dest, &context_for(&link, &dest), symlinked_files) + // we always need to remove the file to be able to create a symlink, + // even if it is writeable. + if dest.is_symlink() || dest.is_file() { + fs::remove_file(dest)?; + } + symlink_file(&link, dest, &context_for(&link, dest), symlinked_files) } /// Generate an error message if `target` is not the correct `target_type` From 733359d48bd44b38af6f0fa3f24263f4bdd0e2d7 Mon Sep 17 00:00:00 2001 From: zhitkoff Date: Mon, 23 Oct 2023 17:44:47 -0400 Subject: [PATCH 0296/2851] split: refactor suffix auto-widening and auto-width --- src/uu/split/src/split.rs | 131 ++++++++++++++++++++++---------------- 1 file changed, 75 insertions(+), 56 deletions(-) diff --git a/src/uu/split/src/split.rs b/src/uu/split/src/split.rs index fff1ccb65ac..2d1701e6089 100644 --- a/src/uu/split/src/split.rs +++ b/src/uu/split/src/split.rs @@ -668,17 +668,38 @@ impl Strategy { } } -/// Parse the suffix type, start and length from the command-line arguments. -/// Determine if the output file names suffix is allowed to auto-widen, -/// i.e. go beyond suffix_length, when more output files need to be written into. -/// Suffix auto-widening rules are: -/// - OFF when suffix length N is specified -/// `-a N` or `--suffix-length=N` -/// - OFF when suffix start number N is specified using long option with value +/// Parse the suffix type, start and length from the command-line arguments +/// as well suffix length auto-widening and auto-width scenarios +/// +/// Suffix auto-widening: Determine if the output file names suffix is allowed to dynamically auto-widen, +/// i.e. change (increase) suffix length dynamically as more files need to be written into. +/// Suffix length auto-widening rules are (in the order they are applied): +/// - ON by default +/// - OFF when suffix start N is specified via long option with a value /// `--numeric-suffixes=N` or `--hex-suffixes=N` -/// - Exception to the above: ON with `-n`/`--number` option (N, K/N, l/N, l/K/N, r/N, r/K/N) +/// - OFF when suffix length N is specified, except for N=0 (see edge cases below) +/// `-a N` or `--suffix-length=N` +/// - OFF if suffix length is auto pre-calculated (auto-width) +/// +/// Suffix auto-width: Determine if the the output file names suffix length should be automatically pre-calculated +/// based on number of files that need to written into, having number of files known upfront +/// Suffix length auto pre-calculation rules: +/// - Pre-calculate new suffix length when `-n`/`--number` option (N, K/N, l/N, l/K/N, r/N, r/K/N) +/// is used, where N is number of chunks = number of files to write into /// and suffix start < N number of files -/// - ON when suffix start number is NOT specified +/// as in `split --numeric-suffixes=1 --number=r/100 file` +/// - Do NOT pre-calculate new suffix length otherwise, i.e. when +/// suffix start >= N number of files +/// as in `split --numeric-suffixes=100 --number=r/100 file` +/// OR when suffix length N is specified, except for N=0 (see edge cases below) +/// `-a N` or `--suffix-length=N` +/// +/// Edge case: +/// - If suffix length is specified as 0 AND `-n`/`--number` option used specifying number of files: +/// set auto widening OFF AND auto pre-calculate required suffix length based on number of files needed +/// - If suffix length is specified as 0 in any other situation +/// keep auto widening ON and suffix length to default value +/// fn suffix_from( matches: &ArgMatches, strategy: &Strategy, @@ -701,18 +722,22 @@ fn suffix_from( ) { (true, _, _, _) => { suffix_type = SuffixType::Decimal; - let suffix_opt = matches.get_one::(OPT_NUMERIC_SUFFIXES); // if option was specified, but without value - this will return None as there is no default value - if suffix_opt.is_some() { - (suffix_start, suffix_auto_widening) = - handle_long_suffix_opt(suffix_opt.unwrap(), strategy, false)?; + let opt = matches.get_one::(OPT_NUMERIC_SUFFIXES); // if option was specified, but without value - this will return None as there is no default value + if opt.is_some() { + suffix_start = opt + .unwrap() + .parse::() + .map_err(|_| SettingsError::SuffixNotParsable(opt.unwrap().to_string()))?; + suffix_auto_widening = false; } } (_, true, _, _) => { suffix_type = SuffixType::Hexadecimal; - let suffix_opt = matches.get_one::(OPT_HEX_SUFFIXES); // if option was specified, but without value - this will return None as there is no default value - if suffix_opt.is_some() { - (suffix_start, suffix_auto_widening) = - handle_long_suffix_opt(suffix_opt.unwrap(), strategy, true)?; + let opt = matches.get_one::(OPT_HEX_SUFFIXES); // if option was specified, but without value - this will return None as there is no default value + if opt.is_some() { + suffix_start = usize::from_str_radix(opt.unwrap(), 16) + .map_err(|_| SettingsError::SuffixNotParsable(opt.unwrap().to_string()))?; + suffix_auto_widening = false; } } (_, _, true, _) => suffix_type = SuffixType::Decimal, // short numeric suffix '-d' @@ -720,17 +745,46 @@ fn suffix_from( _ => suffix_type = SuffixType::Alphabetic, // no numeric/hex suffix, using default alphabetic } + // Get suffix length (could be coming from command line of default value) let suffix_length_str = matches.get_one::(OPT_SUFFIX_LENGTH).unwrap(); // safe to unwrap here as there is default value for this option - let suffix_length: usize = suffix_length_str + let mut suffix_length: usize = suffix_length_str .parse() .map_err(|_| SettingsError::SuffixNotParsable(suffix_length_str.to_string()))?; - // Override suffix_auto_widening if suffix length value came from command line - // and not from default value - if matches.value_source(OPT_SUFFIX_LENGTH) == Some(ValueSource::CommandLine) { + // Disable dynamic auto-widening if suffix length was specified in command line with value > 0 + if matches.value_source(OPT_SUFFIX_LENGTH) == Some(ValueSource::CommandLine) + && suffix_length > 0 + { suffix_auto_widening = false; } + // Auto pre-calculate new suffix length (auto-width) if necessary + if let Strategy::Number(ref number_type) = strategy { + let chunks = number_type.num_chunks(); + let required_suffix_length = ((suffix_start as u64 + chunks) as f64) + .log(suffix_type.radix() as f64) + .ceil() as usize; + + if (suffix_start as u64) < chunks + && !(matches.value_source(OPT_SUFFIX_LENGTH) == Some(ValueSource::CommandLine) + && suffix_length > 0) + { + suffix_auto_widening = false; + suffix_length = required_suffix_length; + } + + if suffix_length < required_suffix_length { + return Err(SettingsError::SuffixTooSmall(required_suffix_length)); + } + } + + // Check suffix length == 0 edge case + // If it is still 0 at this point, then auto-width pre-calculation did not apply + // So, set it to default value and keep auto-widening ON + if suffix_length == 0 { + suffix_length = OPT_DEFAULT_SUFFIX_LENGTH.parse().unwrap(); + } + Ok(( suffix_type, suffix_start, @@ -739,30 +793,6 @@ fn suffix_from( )) } -/// Helper function to [`suffix_from`] function -fn handle_long_suffix_opt( - suffix_opt: &String, - strategy: &Strategy, - is_hex: bool, -) -> Result<(usize, bool), SettingsError> { - let suffix_start = if is_hex { - usize::from_str_radix(suffix_opt, 16) - .map_err(|_| SettingsError::SuffixNotParsable(suffix_opt.to_string()))? - } else { - suffix_opt - .parse::() - .map_err(|_| SettingsError::SuffixNotParsable(suffix_opt.to_string()))? - }; - - let suffix_auto_widening = if let Strategy::Number(ref number_type) = strategy { - let chunks = number_type.num_chunks(); - (suffix_start as u64) < chunks - } else { - false - }; - Ok((suffix_start, suffix_auto_widening)) -} - /// Parameters that control how a file gets split. /// /// You can convert an [`ArgMatches`] instance into a [`Settings`] @@ -877,17 +907,6 @@ impl Settings { let (suffix_type, suffix_start, suffix_auto_widening, suffix_length) = suffix_from(matches, &strategy)?; - if let Strategy::Number(ref number_type) = strategy { - let chunks = number_type.num_chunks(); - if !suffix_auto_widening { - let required_suffix_length = - (chunks as f64).log(suffix_type.radix() as f64).ceil() as usize; - if suffix_length < required_suffix_length { - return Err(SettingsError::SuffixTooSmall(required_suffix_length)); - } - } - } - // Make sure that separator is only one UTF8 character (if specified) // defaults to '\n' - newline character // If the same separator (the same value) was used multiple times - `split` should NOT fail From fbb454a08014a2ac41e3eefccd19f897f6ecf9c4 Mon Sep 17 00:00:00 2001 From: zhitkoff Date: Mon, 23 Oct 2023 19:44:16 -0400 Subject: [PATCH 0297/2851] split: suffix auto-widening and auto-width tests --- src/uu/split/src/split.rs | 7 +++- tests/by-util/test_split.rs | 73 +++++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+), 1 deletion(-) diff --git a/src/uu/split/src/split.rs b/src/uu/split/src/split.rs index 2d1701e6089..4282f1433e8 100644 --- a/src/uu/split/src/split.rs +++ b/src/uu/split/src/split.rs @@ -769,8 +769,13 @@ fn suffix_from( && !(matches.value_source(OPT_SUFFIX_LENGTH) == Some(ValueSource::CommandLine) && suffix_length > 0) { + // with auto-width ON the auto-widening is OFF suffix_auto_widening = false; - suffix_length = required_suffix_length; + + // do not reduce suffix length with auto-width + if suffix_length < required_suffix_length { + suffix_length = required_suffix_length; + } } if suffix_length < required_suffix_length { diff --git a/tests/by-util/test_split.rs b/tests/by-util/test_split.rs index 5760be56097..3ebadde4d09 100644 --- a/tests/by-util/test_split.rs +++ b/tests/by-util/test_split.rs @@ -824,6 +824,79 @@ fn test_hex_dynamic_suffix_length() { assert_eq!(file_read(&at, "xf000"), "a"); } +/// Test for dynamic suffix length (auto-widening) disabled when suffix start number is specified +#[test] +fn test_dynamic_suffix_length_off_with_suffix_start() { + new_ucmd!() + .args(&["-b", "1", "--numeric-suffixes=89", "ninetyonebytes.txt"]) + .fails() + .stderr_only("split: output file suffixes exhausted\n"); +} + +/// Test for dynamic suffix length (auto-widening) enabled when suffix start number is NOT specified +#[test] +fn test_dynamic_suffix_length_on_with_suffix_start_no_value() { + let (at, mut ucmd) = at_and_ucmd!(); + ucmd.args(&["-b", "1", "--numeric-suffixes", "ninetyonebytes.txt"]) + .succeeds(); + assert_eq!(file_read(&at, "x9000"), "a"); +} + +/// Test for suffix auto-width with --number strategy and suffix start number +#[test] +fn test_suffix_auto_width_with_number() { + let (at, mut ucmd) = at_and_ucmd!(); + ucmd.args(&["--numeric-suffixes=1", "--number=r/100", "fivelines.txt"]) + .succeeds(); + let glob = Glob::new(&at, ".", r"x\d\d\d$"); + assert_eq!(glob.count(), 100); + assert_eq!(glob.collate(), at.read_bytes("fivelines.txt")); + assert_eq!(file_read(&at, "x001"), "1\n"); + assert_eq!(file_read(&at, "x100"), ""); + + new_ucmd!() + .args(&["--numeric-suffixes=100", "--number=r/100", "fivelines.txt"]) + .fails(); +} + +/// Test for edge case of specifying 0 for suffix length +#[test] +fn test_suffix_length_zero() { + let (at, mut ucmd) = at_and_ucmd!(); + ucmd.args(&[ + "--numeric-suffixes=1", + "--number=r/100", + "-a", + "0", + "fivelines.txt", + ]) + .succeeds(); + let glob = Glob::new(&at, ".", r"x\d\d\d$"); + assert_eq!(glob.count(), 100); + + new_ucmd!() + .args(&[ + "--numeric-suffixes=100", + "--number=r/100", + "-a", + "0", + "fivelines.txt", + ]) + .fails(); + + new_ucmd!() + .args(&[ + "-b", + "1", + "--numeric-suffixes=89", + "-a", + "0", + "ninetyonebytes.txt", + ]) + .fails() + .stderr_only("split: output file suffixes exhausted\n"); +} + #[test] fn test_suffixes_exhausted() { new_ucmd!() From 8372a3d2ccda7e845fe4b886f20f53b9888c6993 Mon Sep 17 00:00:00 2001 From: zhitkoff Date: Wed, 25 Oct 2023 20:35:35 -0400 Subject: [PATCH 0298/2851] split: refactor filename suffix --- src/uu/split/src/filenames.rs | 374 +++++++++++++++++---- src/uu/split/src/number.rs | 2 +- src/uu/split/src/split.rs | 614 ++-------------------------------- src/uu/split/src/strategy.rs | 379 +++++++++++++++++++++ 4 files changed, 715 insertions(+), 654 deletions(-) create mode 100644 src/uu/split/src/strategy.rs diff --git a/src/uu/split/src/filenames.rs b/src/uu/split/src/filenames.rs index e6a9f19b2b7..e776b274b65 100644 --- a/src/uu/split/src/filenames.rs +++ b/src/uu/split/src/filenames.rs @@ -2,7 +2,7 @@ // // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// spell-checker:ignore zaaa zaab +// spell-checker:ignore zaaa zaab stype //! Compute filenames from a given index. //! //! The [`FilenameIterator`] yields filenames for use with ``split``. @@ -16,18 +16,31 @@ //! use crate::filenames::SuffixType; //! //! let prefix = "chunk_".to_string(); -//! let suffix = ".txt".to_string(); -//! let width = 2; -//! let suffix_type = SuffixType::Alphabetic; -//! let it = FilenameIterator::new(prefix, suffix, width, suffix_type); +//! let suffix = Suffix { +//! stype: SuffixType::Alphabetic, +//! length: 2, +//! start: 0, +//! auto_widening: true, +//! additional: ".txt".to_string(), +//! }; +//! let it = FilenameIterator::new(prefix, suffix); //! //! assert_eq!(it.next().unwrap(), "chunk_aa.txt"); //! assert_eq!(it.next().unwrap(), "chunk_ab.txt"); //! assert_eq!(it.next().unwrap(), "chunk_ac.txt"); //! ``` + use crate::number::DynamicWidthNumber; use crate::number::FixedWidthNumber; use crate::number::Number; +use crate::strategy::Strategy; +use crate::{ + OPT_ADDITIONAL_SUFFIX, OPT_HEX_SUFFIXES, OPT_HEX_SUFFIXES_SHORT, OPT_NUMERIC_SUFFIXES, + OPT_NUMERIC_SUFFIXES_SHORT, OPT_SUFFIX_LENGTH, +}; +use clap::ArgMatches; +use std::fmt; +use uucore::display::Quotable; use uucore::error::{UResult, USimpleError}; /// The format to use for suffixes in the filename for each output chunk. @@ -54,21 +67,200 @@ impl SuffixType { } } +/// Filename suffix parameters +#[derive(Clone)] +pub struct Suffix { + stype: SuffixType, + length: usize, + start: usize, + auto_widening: bool, + additional: String, +} + +/// An error when parsing suffix parameters from command-line arguments. +pub enum SuffixError { + /// Invalid suffix length parameter. + NotParsable(String), + + /// Suffix contains a directory separator, which is not allowed. + ContainsSeparator(String), + + /// Suffix is not large enough to split into specified chunks + TooSmall(usize), +} + +impl fmt::Display for SuffixError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::NotParsable(s) => write!(f, "invalid suffix length: {}", s.quote()), + Self::TooSmall(i) => write!(f, "the suffix length needs to be at least {i}"), + Self::ContainsSeparator(s) => write!( + f, + "invalid suffix {}, contains directory separator", + s.quote() + ), + } + } +} + +impl Suffix { + /// Parse the suffix type, start, length and additional suffix from the command-line arguments + /// as well process suffix length auto-widening and auto-width scenarios + /// + /// Suffix auto-widening: Determine if the output file names suffix is allowed to dynamically auto-widen, + /// i.e. change (increase) suffix length dynamically as more files need to be written into. + /// Suffix length auto-widening rules are (in the order they are applied): + /// - ON by default + /// - OFF when suffix start N is specified via long option with a value + /// `--numeric-suffixes=N` or `--hex-suffixes=N` + /// - OFF when suffix length N is specified, except for N=0 (see edge cases below) + /// `-a N` or `--suffix-length=N` + /// - OFF if suffix length is auto pre-calculated (auto-width) + /// + /// Suffix auto-width: Determine if the the output file names suffix length should be automatically pre-calculated + /// based on number of files that need to written into, having number of files known upfront + /// Suffix length auto pre-calculation rules: + /// - Pre-calculate new suffix length when `-n`/`--number` option (N, K/N, l/N, l/K/N, r/N, r/K/N) + /// is used, where N is number of chunks = number of files to write into + /// and suffix start < N number of files + /// as in `split --numeric-suffixes=1 --number=r/100 file` + /// - Do NOT pre-calculate new suffix length otherwise, i.e. when + /// suffix start >= N number of files + /// as in `split --numeric-suffixes=100 --number=r/100 file` + /// OR when suffix length N is specified, except for N=0 (see edge cases below) + /// `-a N` or `--suffix-length=N` + /// + /// Edge case: + /// - If suffix length is specified as 0 in a command line, + /// first apply auto-width calculations and if still 0 + /// set it to default value. + /// Do NOT change auto-widening value + /// + pub fn from(matches: &ArgMatches, strategy: &Strategy) -> Result { + let stype: SuffixType; + + // Defaults + let mut start = 0; + let mut auto_widening = true; + let default_length: usize = 2; + + // Check if the user is specifying one or more than one suffix + // Any combination of suffixes is allowed + // Since all suffixes are setup with 'overrides_with_all()' against themselves and each other, + // last one wins, all others are ignored + match ( + matches.contains_id(OPT_NUMERIC_SUFFIXES), + matches.contains_id(OPT_HEX_SUFFIXES), + matches.get_flag(OPT_NUMERIC_SUFFIXES_SHORT), + matches.get_flag(OPT_HEX_SUFFIXES_SHORT), + ) { + (true, _, _, _) => { + stype = SuffixType::Decimal; + // if option was specified, but without value - this will return None as there is no default value + if let Some(opt) = matches.get_one::(OPT_NUMERIC_SUFFIXES) { + start = opt + .parse::() + .map_err(|_| SuffixError::NotParsable(opt.to_string()))?; + auto_widening = false; + } + } + (_, true, _, _) => { + stype = SuffixType::Hexadecimal; + // if option was specified, but without value - this will return None as there is no default value + if let Some(opt) = matches.get_one::(OPT_HEX_SUFFIXES) { + start = usize::from_str_radix(opt, 16) + .map_err(|_| SuffixError::NotParsable(opt.to_string()))?; + auto_widening = false; + } + } + (_, _, true, _) => stype = SuffixType::Decimal, // short numeric suffix '-d' + (_, _, _, true) => stype = SuffixType::Hexadecimal, // short hex suffix '-x' + _ => stype = SuffixType::Alphabetic, // no numeric/hex suffix, using default alphabetic + } + + // Get suffix length and a flag to indicate if it was specified with command line option + let (mut length, is_length_cmd_opt) = + if let Some(v) = matches.get_one::(OPT_SUFFIX_LENGTH) { + // suffix length was specified in command line + ( + v.parse::() + .map_err(|_| SuffixError::NotParsable(v.to_string()))?, + true, + ) + } else { + // no suffix length option was specified in command line + // set to default value + (default_length, false) + }; + + // Disable dynamic auto-widening if suffix length was specified in command line with value > 0 + if is_length_cmd_opt && length > 0 { + auto_widening = false; + } + + // Auto pre-calculate new suffix length (auto-width) if necessary + if let Strategy::Number(ref number_type) = strategy { + let chunks = number_type.num_chunks(); + let required_length = ((start as u64 + chunks) as f64) + .log(stype.radix() as f64) + .ceil() as usize; + + if (start as u64) < chunks && !(is_length_cmd_opt && length > 0) { + // with auto-width ON the auto-widening is OFF + auto_widening = false; + + // do not reduce suffix length with auto-width + if length < required_length { + length = required_length; + } + } + + if length < required_length { + return Err(SuffixError::TooSmall(required_length)); + } + } + + // Check edge case when suffix length == 0 was specified in command line + // Set it to default value + if is_length_cmd_opt && length == 0 { + length = default_length; + } + + let additional = matches + .get_one::(OPT_ADDITIONAL_SUFFIX) + .unwrap() + .to_string(); + if additional.contains('/') { + return Err(SuffixError::ContainsSeparator(additional)); + } + + let result = Self { + stype, + length, + start, + auto_widening, + additional, + }; + + Ok(result) + } +} + /// Compute filenames from a given index. /// /// This iterator yields filenames for use with ``split``. /// /// The `prefix` is prepended to each filename and the -/// `additional_suffix1` is appended to each filename. +/// `suffix.additional` is appended to each filename. /// -/// If `suffix_length` is 0, then the variable portion of the filename +/// If `suffix.auto_widening` is true, then the variable portion of the filename /// that identifies the current chunk will have a dynamically -/// increasing width. If `suffix_length` is greater than zero, then -/// the variable portion of the filename will always be exactly that +/// increasing width. If `suffix.auto_widening` is false, then +/// the variable portion of the filename will always be exactly `suffix.length` /// width in characters. In that case, after the iterator yields each /// string of that width, the iterator is exhausted. /// -/// Finally, `suffix_type` controls which type of suffix to produce, +/// Finally, `suffix.stype` controls which type of suffix to produce, /// alphabetic or numeric. /// /// # Examples @@ -81,10 +273,14 @@ impl SuffixType { /// use crate::filenames::SuffixType; /// /// let prefix = "chunk_".to_string(); -/// let suffix = ".txt".to_string(); -/// let width = 2; -/// let suffix_type = SuffixType::Alphabetic; -/// let it = FilenameIterator::new(prefix, suffix, width, suffix_type); +/// let suffix = Suffix { +/// stype: SuffixType::Alphabetic, +/// length: 2, +/// start: 0, +/// auto_widening: true, +/// additional: ".txt".to_string(), +/// }; +/// let it = FilenameIterator::new(prefix, suffix); /// /// assert_eq!(it.next().unwrap(), "chunk_aa.txt"); /// assert_eq!(it.next().unwrap(), "chunk_ab.txt"); @@ -98,37 +294,34 @@ impl SuffixType { /// use crate::filenames::SuffixType; /// /// let prefix = "chunk_".to_string(); -/// let suffix = ".txt".to_string(); -/// let width = 2; -/// let suffix_type = SuffixType::Decimal; -/// let it = FilenameIterator::new(prefix, suffix, width, suffix_type); +/// let suffix = Suffix { +/// stype: SuffixType::Decimal, +/// length: 2, +/// start: 0, +/// auto_widening: true, +/// additional: ".txt".to_string(), +/// }; +/// let it = FilenameIterator::new(prefix, suffix); /// /// assert_eq!(it.next().unwrap(), "chunk_00.txt"); /// assert_eq!(it.next().unwrap(), "chunk_01.txt"); /// assert_eq!(it.next().unwrap(), "chunk_02.txt"); /// ``` pub struct FilenameIterator<'a> { - additional_suffix: &'a str, prefix: &'a str, + additional_suffix: &'a str, number: Number, first_iteration: bool, } impl<'a> FilenameIterator<'a> { - pub fn new( - prefix: &'a str, - additional_suffix: &'a str, - suffix_length: usize, - suffix_type: SuffixType, - suffix_start: usize, - suffix_auto_widening: bool, - ) -> UResult> { - let radix = suffix_type.radix(); - let number = if suffix_auto_widening { - Number::DynamicWidth(DynamicWidthNumber::new(radix, suffix_start)) + pub fn new(prefix: &'a str, suffix: &'a Suffix) -> UResult> { + let radix = suffix.stype.radix(); + let number = if suffix.auto_widening { + Number::DynamicWidth(DynamicWidthNumber::new(radix, suffix.start)) } else { Number::FixedWidth( - FixedWidthNumber::new(radix, suffix_length, suffix_start).map_err(|_| { + FixedWidthNumber::new(radix, suffix.length, suffix.start).map_err(|_| { USimpleError::new( 1, "numerical suffix start value is too large for the suffix length", @@ -136,6 +329,7 @@ impl<'a> FilenameIterator<'a> { })?, ) }; + let additional_suffix = suffix.additional.as_str(); Ok(FilenameIterator { prefix, @@ -168,46 +362,62 @@ impl<'a> Iterator for FilenameIterator<'a> { mod tests { use crate::filenames::FilenameIterator; + use crate::filenames::Suffix; use crate::filenames::SuffixType; #[test] fn test_filename_iterator_alphabetic_fixed_width() { - let mut it = - FilenameIterator::new("chunk_", ".txt", 2, SuffixType::Alphabetic, 0, false).unwrap(); + let suffix = Suffix { + stype: SuffixType::Alphabetic, + length: 2, + start: 0, + auto_widening: false, + additional: ".txt".to_string(), + }; + let mut it = FilenameIterator::new("chunk_", &suffix).unwrap(); assert_eq!(it.next().unwrap(), "chunk_aa.txt"); assert_eq!(it.next().unwrap(), "chunk_ab.txt"); assert_eq!(it.next().unwrap(), "chunk_ac.txt"); - let mut it = - FilenameIterator::new("chunk_", ".txt", 2, SuffixType::Alphabetic, 0, false).unwrap(); + let mut it = FilenameIterator::new("chunk_", &suffix).unwrap(); assert_eq!(it.nth(26 * 26 - 1).unwrap(), "chunk_zz.txt"); assert_eq!(it.next(), None); } #[test] fn test_filename_iterator_numeric_fixed_width() { - let mut it = - FilenameIterator::new("chunk_", ".txt", 2, SuffixType::Decimal, 0, false).unwrap(); + let suffix = Suffix { + stype: SuffixType::Decimal, + length: 2, + start: 0, + auto_widening: false, + additional: ".txt".to_string(), + }; + let mut it = FilenameIterator::new("chunk_", &suffix).unwrap(); assert_eq!(it.next().unwrap(), "chunk_00.txt"); assert_eq!(it.next().unwrap(), "chunk_01.txt"); assert_eq!(it.next().unwrap(), "chunk_02.txt"); - let mut it = - FilenameIterator::new("chunk_", ".txt", 2, SuffixType::Decimal, 0, false).unwrap(); + let mut it = FilenameIterator::new("chunk_", &suffix).unwrap(); assert_eq!(it.nth(10 * 10 - 1).unwrap(), "chunk_99.txt"); assert_eq!(it.next(), None); } #[test] fn test_filename_iterator_alphabetic_dynamic_width() { - let mut it = - FilenameIterator::new("chunk_", ".txt", 2, SuffixType::Alphabetic, 0, true).unwrap(); + let suffix = Suffix { + stype: SuffixType::Alphabetic, + length: 2, + start: 0, + auto_widening: true, + additional: ".txt".to_string(), + }; + let mut it = FilenameIterator::new("chunk_", &suffix).unwrap(); assert_eq!(it.next().unwrap(), "chunk_aa.txt"); assert_eq!(it.next().unwrap(), "chunk_ab.txt"); assert_eq!(it.next().unwrap(), "chunk_ac.txt"); - let mut it = - FilenameIterator::new("chunk_", ".txt", 2, SuffixType::Alphabetic, 0, true).unwrap(); + let mut it = FilenameIterator::new("chunk_", &suffix).unwrap(); assert_eq!(it.nth(26 * 25 - 1).unwrap(), "chunk_yz.txt"); assert_eq!(it.next().unwrap(), "chunk_zaaa.txt"); assert_eq!(it.next().unwrap(), "chunk_zaab.txt"); @@ -215,54 +425,96 @@ mod tests { #[test] fn test_filename_iterator_numeric_dynamic_width() { - let mut it = - FilenameIterator::new("chunk_", ".txt", 2, SuffixType::Decimal, 0, true).unwrap(); + let suffix = Suffix { + stype: SuffixType::Decimal, + length: 2, + start: 0, + auto_widening: true, + additional: ".txt".to_string(), + }; + let mut it = FilenameIterator::new("chunk_", &suffix).unwrap(); assert_eq!(it.next().unwrap(), "chunk_00.txt"); assert_eq!(it.next().unwrap(), "chunk_01.txt"); assert_eq!(it.next().unwrap(), "chunk_02.txt"); - let mut it = - FilenameIterator::new("chunk_", ".txt", 2, SuffixType::Decimal, 0, true).unwrap(); + let mut it = FilenameIterator::new("chunk_", &suffix).unwrap(); assert_eq!(it.nth(10 * 9 - 1).unwrap(), "chunk_89.txt"); assert_eq!(it.next().unwrap(), "chunk_9000.txt"); assert_eq!(it.next().unwrap(), "chunk_9001.txt"); } #[test] - fn test_filename_iterator_numeric_suffix_decimal() { - let mut it = - FilenameIterator::new("chunk_", ".txt", 2, SuffixType::Decimal, 5, true).unwrap(); + fn test_filename_iterator_numeric_decimal() { + let suffix = Suffix { + stype: SuffixType::Decimal, + length: 2, + start: 5, + auto_widening: true, + additional: ".txt".to_string(), + }; + let mut it = FilenameIterator::new("chunk_", &suffix).unwrap(); assert_eq!(it.next().unwrap(), "chunk_05.txt"); assert_eq!(it.next().unwrap(), "chunk_06.txt"); assert_eq!(it.next().unwrap(), "chunk_07.txt"); } #[test] - fn test_filename_iterator_numeric_suffix_hex() { - let mut it = - FilenameIterator::new("chunk_", ".txt", 2, SuffixType::Hexadecimal, 9, true).unwrap(); + fn test_filename_iterator_numeric_hex() { + let suffix = Suffix { + stype: SuffixType::Hexadecimal, + length: 2, + start: 9, + auto_widening: true, + additional: ".txt".to_string(), + }; + let mut it = FilenameIterator::new("chunk_", &suffix).unwrap(); assert_eq!(it.next().unwrap(), "chunk_09.txt"); assert_eq!(it.next().unwrap(), "chunk_0a.txt"); assert_eq!(it.next().unwrap(), "chunk_0b.txt"); } #[test] - fn test_filename_iterator_numeric_suffix_err() { - let mut it = - FilenameIterator::new("chunk_", ".txt", 3, SuffixType::Decimal, 999, false).unwrap(); + fn test_filename_iterator_numeric_err() { + let suffix = Suffix { + stype: SuffixType::Decimal, + length: 3, + start: 999, + auto_widening: false, + additional: ".txt".to_string(), + }; + let mut it = FilenameIterator::new("chunk_", &suffix).unwrap(); assert_eq!(it.next().unwrap(), "chunk_999.txt"); assert!(it.next().is_none()); - let it = FilenameIterator::new("chunk_", ".txt", 3, SuffixType::Decimal, 1000, false); + let suffix = Suffix { + stype: SuffixType::Decimal, + length: 3, + start: 1000, + auto_widening: false, + additional: ".txt".to_string(), + }; + let it = FilenameIterator::new("chunk_", &suffix); assert!(it.is_err()); - let mut it = - FilenameIterator::new("chunk_", ".txt", 3, SuffixType::Hexadecimal, 0xfff, false) - .unwrap(); + let suffix = Suffix { + stype: SuffixType::Hexadecimal, + length: 3, + start: 0xfff, + auto_widening: false, + additional: ".txt".to_string(), + }; + let mut it = FilenameIterator::new("chunk_", &suffix).unwrap(); assert_eq!(it.next().unwrap(), "chunk_fff.txt"); assert!(it.next().is_none()); - let it = FilenameIterator::new("chunk_", ".txt", 3, SuffixType::Hexadecimal, 0x1000, false); + let suffix = Suffix { + stype: SuffixType::Hexadecimal, + length: 3, + start: 0x1000, + auto_widening: false, + additional: ".txt".to_string(), + }; + let it = FilenameIterator::new("chunk_", &suffix); assert!(it.is_err()); } } diff --git a/src/uu/split/src/number.rs b/src/uu/split/src/number.rs index a01701c80e3..6312d0a3fa6 100644 --- a/src/uu/split/src/number.rs +++ b/src/uu/split/src/number.rs @@ -2,7 +2,7 @@ // // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// spell-checker:ignore zaaa zaab +// spell-checker:ignore zaaa zaab feff //! A number in arbitrary radix expressed in a positional notation. //! //! Use the [`Number`] enum to represent an arbitrary number in an diff --git a/src/uu/split/src/split.rs b/src/uu/split/src/split.rs index 4282f1433e8..4b5cd920715 100644 --- a/src/uu/split/src/split.rs +++ b/src/uu/split/src/split.rs @@ -8,9 +8,10 @@ mod filenames; mod number; mod platform; +mod strategy; -use crate::filenames::FilenameIterator; -use crate::filenames::SuffixType; +use crate::filenames::{FilenameIterator, Suffix, SuffixError}; +use crate::strategy::{NumberType, Strategy, StrategyError}; use clap::{crate_version, parser::ValueSource, Arg, ArgAction, ArgMatches, Command, ValueHint}; use std::env; use std::ffi::OsString; @@ -22,7 +23,7 @@ use std::path::Path; use std::u64; use uucore::display::Quotable; use uucore::error::{FromIo, UIoError, UResult, USimpleError, UUsageError}; -use uucore::parse_size::{parse_size_u64, parse_size_u64_max, ParseSizeError}; + use uucore::uio_error; use uucore::{format_usage, help_about, help_section, help_usage}; @@ -37,8 +38,6 @@ static OPT_NUMERIC_SUFFIXES_SHORT: &str = "-d"; static OPT_HEX_SUFFIXES: &str = "hex-suffixes"; static OPT_HEX_SUFFIXES_SHORT: &str = "-x"; static OPT_SUFFIX_LENGTH: &str = "suffix-length"; -// If no suffix length is specified, default to "2" characters following GNU split behavior -static OPT_DEFAULT_SUFFIX_LENGTH: &str = "2"; static OPT_VERBOSE: &str = "verbose"; static OPT_SEPARATOR: &str = "separator"; //The ---io and ---io-blksize parameters are consumed and ignored. @@ -357,7 +356,6 @@ pub fn uu_app() -> Command { .long(OPT_SUFFIX_LENGTH) .allow_hyphen_values(true) .value_name("N") - .default_value(OPT_DEFAULT_SUFFIX_LENGTH) .help("generate suffixes of length N (default 2)"), ) .arg( @@ -398,418 +396,13 @@ pub fn uu_app() -> Command { ) } -/// Sub-strategy to use when splitting a file into a specific number of chunks. -#[derive(Debug, PartialEq)] -enum NumberType { - /// Split into a specific number of chunks by byte. - Bytes(u64), - - /// Split into a specific number of chunks by byte - /// but output only the *k*th chunk. - KthBytes(u64, u64), - - /// Split into a specific number of chunks by line (approximately). - Lines(u64), - - /// Split into a specific number of chunks by line - /// (approximately), but output only the *k*th chunk. - KthLines(u64, u64), - - /// Assign lines via round-robin to the specified number of output chunks. - RoundRobin(u64), - - /// Assign lines via round-robin to the specified number of output - /// chunks, but output only the *k*th chunk. - KthRoundRobin(u64, u64), -} - -impl NumberType { - /// The number of chunks for this number type. - fn num_chunks(&self) -> u64 { - match self { - Self::Bytes(n) => *n, - Self::KthBytes(_, n) => *n, - Self::Lines(n) => *n, - Self::KthLines(_, n) => *n, - Self::RoundRobin(n) => *n, - Self::KthRoundRobin(_, n) => *n, - } - } -} - -/// An error due to an invalid parameter to the `-n` command-line option. -#[derive(Debug, PartialEq)] -enum NumberTypeError { - /// The number of chunks was invalid. - /// - /// This can happen if the value of `N` in any of the following - /// command-line options is not a positive integer: - /// - /// ```ignore - /// -n N - /// -n K/N - /// -n l/N - /// -n l/K/N - /// -n r/N - /// -n r/K/N - /// ``` - NumberOfChunks(String), - - /// The chunk number was invalid. - /// - /// This can happen if the value of `K` in any of the following - /// command-line options is not a positive integer - /// or if `K` is 0 - /// or if `K` is greater than `N`: - /// - /// ```ignore - /// -n K/N - /// -n l/K/N - /// -n r/K/N - /// ``` - ChunkNumber(String), -} - -impl NumberType { - /// Parse a `NumberType` from a string. - /// - /// The following strings are valid arguments: - /// - /// ```ignore - /// "N" - /// "K/N" - /// "l/N" - /// "l/K/N" - /// "r/N" - /// "r/K/N" - /// ``` - /// - /// The `N` represents the number of chunks and the `K` represents - /// a chunk number. - /// - /// # Errors - /// - /// If the string is not one of the valid number types, - /// if `K` is not a nonnegative integer, - /// or if `K` is 0, - /// or if `N` is not a positive integer, - /// or if `K` is greater than `N` - /// then this function returns [`NumberTypeError`]. - fn from(s: &str) -> Result { - fn is_invalid_chunk(chunk_number: u64, num_chunks: u64) -> bool { - chunk_number > num_chunks || chunk_number == 0 - } - let parts: Vec<&str> = s.split('/').collect(); - match &parts[..] { - [n_str] => { - let num_chunks = parse_size_u64(n_str) - .map_err(|_| NumberTypeError::NumberOfChunks(n_str.to_string()))?; - if num_chunks > 0 { - Ok(Self::Bytes(num_chunks)) - } else { - Err(NumberTypeError::NumberOfChunks(s.to_string())) - } - } - [k_str, n_str] if !k_str.starts_with('l') && !k_str.starts_with('r') => { - let num_chunks = parse_size_u64(n_str) - .map_err(|_| NumberTypeError::NumberOfChunks(n_str.to_string()))?; - let chunk_number = parse_size_u64(k_str) - .map_err(|_| NumberTypeError::ChunkNumber(k_str.to_string()))?; - if is_invalid_chunk(chunk_number, num_chunks) { - return Err(NumberTypeError::ChunkNumber(k_str.to_string())); - } - Ok(Self::KthBytes(chunk_number, num_chunks)) - } - ["l", n_str] => { - let num_chunks = parse_size_u64(n_str) - .map_err(|_| NumberTypeError::NumberOfChunks(n_str.to_string()))?; - Ok(Self::Lines(num_chunks)) - } - ["l", k_str, n_str] => { - let num_chunks = parse_size_u64(n_str) - .map_err(|_| NumberTypeError::NumberOfChunks(n_str.to_string()))?; - let chunk_number = parse_size_u64(k_str) - .map_err(|_| NumberTypeError::ChunkNumber(k_str.to_string()))?; - if is_invalid_chunk(chunk_number, num_chunks) { - return Err(NumberTypeError::ChunkNumber(k_str.to_string())); - } - Ok(Self::KthLines(chunk_number, num_chunks)) - } - ["r", n_str] => { - let num_chunks = parse_size_u64(n_str) - .map_err(|_| NumberTypeError::NumberOfChunks(n_str.to_string()))?; - Ok(Self::RoundRobin(num_chunks)) - } - ["r", k_str, n_str] => { - let num_chunks = parse_size_u64(n_str) - .map_err(|_| NumberTypeError::NumberOfChunks(n_str.to_string()))?; - let chunk_number = parse_size_u64(k_str) - .map_err(|_| NumberTypeError::ChunkNumber(k_str.to_string()))?; - if is_invalid_chunk(chunk_number, num_chunks) { - return Err(NumberTypeError::ChunkNumber(k_str.to_string())); - } - Ok(Self::KthRoundRobin(chunk_number, num_chunks)) - } - _ => Err(NumberTypeError::NumberOfChunks(s.to_string())), - } - } -} - -/// The strategy for breaking up the input file into chunks. -enum Strategy { - /// Each chunk has the specified number of lines. - Lines(u64), - - /// Each chunk has the specified number of bytes. - Bytes(u64), - - /// Each chunk has as many lines as possible without exceeding the - /// specified number of bytes. - LineBytes(u64), - - /// Split the file into this many chunks. - /// - /// There are several sub-strategies available, as defined by - /// [`NumberType`]. - Number(NumberType), -} - -/// An error when parsing a chunking strategy from command-line arguments. -enum StrategyError { - /// Invalid number of lines. - Lines(ParseSizeError), - - /// Invalid number of bytes. - Bytes(ParseSizeError), - - /// Invalid number type. - NumberType(NumberTypeError), - - /// Multiple chunking strategies were specified (but only one should be). - MultipleWays, -} - -impl fmt::Display for StrategyError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - Self::Lines(e) => write!(f, "invalid number of lines: {e}"), - Self::Bytes(e) => write!(f, "invalid number of bytes: {e}"), - Self::NumberType(NumberTypeError::NumberOfChunks(s)) => { - write!(f, "invalid number of chunks: {s}") - } - Self::NumberType(NumberTypeError::ChunkNumber(s)) => { - write!(f, "invalid chunk number: {s}") - } - Self::MultipleWays => write!(f, "cannot split in more than one way"), - } - } -} - -impl Strategy { - /// Parse a strategy from the command-line arguments. - fn from(matches: &ArgMatches, obs_lines: &Option) -> Result { - fn get_and_parse( - matches: &ArgMatches, - option: &str, - strategy: fn(u64) -> Strategy, - error: fn(ParseSizeError) -> StrategyError, - ) -> Result { - let s = matches.get_one::(option).unwrap(); - let n = parse_size_u64_max(s).map_err(error)?; - if n > 0 { - Ok(strategy(n)) - } else { - Err(error(ParseSizeError::ParseFailure(s.to_string()))) - } - } - // Check that the user is not specifying more than one strategy. - // - // Note: right now, this exact behavior cannot be handled by - // overrides_with_all() due to obsolete lines value option - match ( - obs_lines, - matches.value_source(OPT_LINES) == Some(ValueSource::CommandLine), - matches.value_source(OPT_BYTES) == Some(ValueSource::CommandLine), - matches.value_source(OPT_LINE_BYTES) == Some(ValueSource::CommandLine), - matches.value_source(OPT_NUMBER) == Some(ValueSource::CommandLine), - ) { - (Some(v), false, false, false, false) => { - let v = parse_size_u64_max(v).map_err(|_| { - StrategyError::Lines(ParseSizeError::ParseFailure(v.to_string())) - })?; - if v > 0 { - Ok(Self::Lines(v)) - } else { - Err(StrategyError::Lines(ParseSizeError::ParseFailure( - v.to_string(), - ))) - } - } - (None, false, false, false, false) => Ok(Self::Lines(1000)), - (None, true, false, false, false) => { - get_and_parse(matches, OPT_LINES, Self::Lines, StrategyError::Lines) - } - (None, false, true, false, false) => { - get_and_parse(matches, OPT_BYTES, Self::Bytes, StrategyError::Bytes) - } - (None, false, false, true, false) => get_and_parse( - matches, - OPT_LINE_BYTES, - Self::LineBytes, - StrategyError::Bytes, - ), - (None, false, false, false, true) => { - let s = matches.get_one::(OPT_NUMBER).unwrap(); - let number_type = NumberType::from(s).map_err(StrategyError::NumberType)?; - Ok(Self::Number(number_type)) - } - _ => Err(StrategyError::MultipleWays), - } - } -} - -/// Parse the suffix type, start and length from the command-line arguments -/// as well suffix length auto-widening and auto-width scenarios -/// -/// Suffix auto-widening: Determine if the output file names suffix is allowed to dynamically auto-widen, -/// i.e. change (increase) suffix length dynamically as more files need to be written into. -/// Suffix length auto-widening rules are (in the order they are applied): -/// - ON by default -/// - OFF when suffix start N is specified via long option with a value -/// `--numeric-suffixes=N` or `--hex-suffixes=N` -/// - OFF when suffix length N is specified, except for N=0 (see edge cases below) -/// `-a N` or `--suffix-length=N` -/// - OFF if suffix length is auto pre-calculated (auto-width) -/// -/// Suffix auto-width: Determine if the the output file names suffix length should be automatically pre-calculated -/// based on number of files that need to written into, having number of files known upfront -/// Suffix length auto pre-calculation rules: -/// - Pre-calculate new suffix length when `-n`/`--number` option (N, K/N, l/N, l/K/N, r/N, r/K/N) -/// is used, where N is number of chunks = number of files to write into -/// and suffix start < N number of files -/// as in `split --numeric-suffixes=1 --number=r/100 file` -/// - Do NOT pre-calculate new suffix length otherwise, i.e. when -/// suffix start >= N number of files -/// as in `split --numeric-suffixes=100 --number=r/100 file` -/// OR when suffix length N is specified, except for N=0 (see edge cases below) -/// `-a N` or `--suffix-length=N` -/// -/// Edge case: -/// - If suffix length is specified as 0 AND `-n`/`--number` option used specifying number of files: -/// set auto widening OFF AND auto pre-calculate required suffix length based on number of files needed -/// - If suffix length is specified as 0 in any other situation -/// keep auto widening ON and suffix length to default value -/// -fn suffix_from( - matches: &ArgMatches, - strategy: &Strategy, -) -> Result<(SuffixType, usize, bool, usize), SettingsError> { - let suffix_type: SuffixType; - - // Defaults - let mut suffix_start = 0; - let mut suffix_auto_widening = true; - - // Check if the user is specifying one or more than one suffix - // Any combination of suffixes is allowed - // Since all suffixes are setup with 'overrides_with_all()' against themselves and each other, - // last one wins, all others are ignored - match ( - matches.contains_id(OPT_NUMERIC_SUFFIXES), - matches.contains_id(OPT_HEX_SUFFIXES), - matches.get_flag(OPT_NUMERIC_SUFFIXES_SHORT), - matches.get_flag(OPT_HEX_SUFFIXES_SHORT), - ) { - (true, _, _, _) => { - suffix_type = SuffixType::Decimal; - let opt = matches.get_one::(OPT_NUMERIC_SUFFIXES); // if option was specified, but without value - this will return None as there is no default value - if opt.is_some() { - suffix_start = opt - .unwrap() - .parse::() - .map_err(|_| SettingsError::SuffixNotParsable(opt.unwrap().to_string()))?; - suffix_auto_widening = false; - } - } - (_, true, _, _) => { - suffix_type = SuffixType::Hexadecimal; - let opt = matches.get_one::(OPT_HEX_SUFFIXES); // if option was specified, but without value - this will return None as there is no default value - if opt.is_some() { - suffix_start = usize::from_str_radix(opt.unwrap(), 16) - .map_err(|_| SettingsError::SuffixNotParsable(opt.unwrap().to_string()))?; - suffix_auto_widening = false; - } - } - (_, _, true, _) => suffix_type = SuffixType::Decimal, // short numeric suffix '-d' - (_, _, _, true) => suffix_type = SuffixType::Hexadecimal, // short hex suffix '-x' - _ => suffix_type = SuffixType::Alphabetic, // no numeric/hex suffix, using default alphabetic - } - - // Get suffix length (could be coming from command line of default value) - let suffix_length_str = matches.get_one::(OPT_SUFFIX_LENGTH).unwrap(); // safe to unwrap here as there is default value for this option - let mut suffix_length: usize = suffix_length_str - .parse() - .map_err(|_| SettingsError::SuffixNotParsable(suffix_length_str.to_string()))?; - - // Disable dynamic auto-widening if suffix length was specified in command line with value > 0 - if matches.value_source(OPT_SUFFIX_LENGTH) == Some(ValueSource::CommandLine) - && suffix_length > 0 - { - suffix_auto_widening = false; - } - - // Auto pre-calculate new suffix length (auto-width) if necessary - if let Strategy::Number(ref number_type) = strategy { - let chunks = number_type.num_chunks(); - let required_suffix_length = ((suffix_start as u64 + chunks) as f64) - .log(suffix_type.radix() as f64) - .ceil() as usize; - - if (suffix_start as u64) < chunks - && !(matches.value_source(OPT_SUFFIX_LENGTH) == Some(ValueSource::CommandLine) - && suffix_length > 0) - { - // with auto-width ON the auto-widening is OFF - suffix_auto_widening = false; - - // do not reduce suffix length with auto-width - if suffix_length < required_suffix_length { - suffix_length = required_suffix_length; - } - } - - if suffix_length < required_suffix_length { - return Err(SettingsError::SuffixTooSmall(required_suffix_length)); - } - } - - // Check suffix length == 0 edge case - // If it is still 0 at this point, then auto-width pre-calculation did not apply - // So, set it to default value and keep auto-widening ON - if suffix_length == 0 { - suffix_length = OPT_DEFAULT_SUFFIX_LENGTH.parse().unwrap(); - } - - Ok(( - suffix_type, - suffix_start, - suffix_auto_widening, - suffix_length, - )) -} - /// Parameters that control how a file gets split. /// /// You can convert an [`ArgMatches`] instance into a [`Settings`] /// instance by calling [`Settings::from`]. struct Settings { prefix: String, - suffix_type: SuffixType, - suffix_length: usize, - suffix_start: usize, - /// Whether or not suffix length should automatically widen - suffix_auto_widening: bool, - additional_suffix: String, + suffix: Suffix, input: String, /// When supplied, a shell command to output to instead of xaa, xab … filter: Option, @@ -834,13 +427,7 @@ enum SettingsError { Strategy(StrategyError), /// Invalid suffix length parameter. - SuffixNotParsable(String), - - /// Suffix contains a directory separator, which is not allowed. - SuffixContainsSeparator(String), - - /// Suffix is not large enough to split into specified chunks - SuffixTooSmall(usize), + Suffix(SuffixError), /// Multi-character (Invalid) separator MultiCharacterSeparator(String), @@ -864,7 +451,8 @@ impl SettingsError { fn requires_usage(&self) -> bool { matches!( self, - Self::Strategy(StrategyError::MultipleWays) | Self::SuffixContainsSeparator(_) + Self::Strategy(StrategyError::MultipleWays) + | Self::Suffix(SuffixError::ContainsSeparator(_)) ) } } @@ -873,19 +461,13 @@ impl fmt::Display for SettingsError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Self::Strategy(e) => e.fmt(f), - Self::SuffixNotParsable(s) => write!(f, "invalid suffix length: {}", s.quote()), - Self::SuffixTooSmall(i) => write!(f, "the suffix length needs to be at least {i}"), + Self::Suffix(e) => e.fmt(f), Self::MultiCharacterSeparator(s) => { write!(f, "multi-character separator {}", s.quote()) } Self::MultipleSeparatorCharacters => { write!(f, "multiple separator characters specified") } - Self::SuffixContainsSeparator(s) => write!( - f, - "invalid suffix {}, contains directory separator", - s.quote() - ), Self::FilterWithKthChunkNumber => { write!(f, "--filter does not process a chunk extracted to stdout") } @@ -901,16 +483,8 @@ impl fmt::Display for SettingsError { impl Settings { /// Parse a strategy from the command-line arguments. fn from(matches: &ArgMatches, obs_lines: &Option) -> Result { - let additional_suffix = matches - .get_one::(OPT_ADDITIONAL_SUFFIX) - .unwrap() - .to_string(); - if additional_suffix.contains('/') { - return Err(SettingsError::SuffixContainsSeparator(additional_suffix)); - } let strategy = Strategy::from(matches, obs_lines).map_err(SettingsError::Strategy)?; - let (suffix_type, suffix_start, suffix_auto_widening, suffix_length) = - suffix_from(matches, &strategy)?; + let suffix = Suffix::from(matches, &strategy).map_err(SettingsError::Suffix)?; // Make sure that separator is only one UTF8 character (if specified) // defaults to '\n' - newline character @@ -932,17 +506,13 @@ impl Settings { }; let result = Self { - suffix_length, - suffix_type, - suffix_start, - suffix_auto_widening, - additional_suffix, - verbose: matches.value_source(OPT_VERBOSE) == Some(ValueSource::CommandLine), - separator, - strategy, - input: matches.get_one::(ARG_INPUT).unwrap().to_owned(), prefix: matches.get_one::(ARG_PREFIX).unwrap().to_owned(), + suffix, + input: matches.get_one::(ARG_INPUT).unwrap().to_owned(), filter: matches.get_one::(OPT_FILTER).map(|s| s.to_owned()), + strategy, + verbose: matches.value_source(OPT_VERBOSE) == Some(ValueSource::CommandLine), + separator, elide_empty_files: matches.get_flag(OPT_ELIDE_EMPTY_FILES), }; @@ -1059,14 +629,7 @@ struct ByteChunkWriter<'a> { impl<'a> ByteChunkWriter<'a> { fn new(chunk_size: u64, settings: &'a Settings) -> UResult> { - let mut filename_iterator = FilenameIterator::new( - &settings.prefix, - &settings.additional_suffix, - settings.suffix_length, - settings.suffix_type, - settings.suffix_start, - settings.suffix_auto_widening, - )?; + let mut filename_iterator = FilenameIterator::new(&settings.prefix, &settings.suffix)?; let filename = filename_iterator .next() .ok_or_else(|| USimpleError::new(1, "output file suffixes exhausted"))?; @@ -1190,14 +753,7 @@ struct LineChunkWriter<'a> { impl<'a> LineChunkWriter<'a> { fn new(chunk_size: u64, settings: &'a Settings) -> UResult> { - let mut filename_iterator = FilenameIterator::new( - &settings.prefix, - &settings.additional_suffix, - settings.suffix_length, - settings.suffix_type, - settings.suffix_start, - settings.suffix_auto_widening, - )?; + let mut filename_iterator = FilenameIterator::new(&settings.prefix, &settings.suffix)?; let filename = filename_iterator .next() .ok_or_else(|| USimpleError::new(1, "output file suffixes exhausted"))?; @@ -1304,14 +860,7 @@ struct LineBytesChunkWriter<'a> { impl<'a> LineBytesChunkWriter<'a> { fn new(chunk_size: u64, settings: &'a Settings) -> UResult> { - let mut filename_iterator = FilenameIterator::new( - &settings.prefix, - &settings.additional_suffix, - settings.suffix_length, - settings.suffix_type, - settings.suffix_start, - settings.suffix_auto_widening, - )?; + let mut filename_iterator = FilenameIterator::new(&settings.prefix, &settings.suffix)?; let filename = filename_iterator .next() .ok_or_else(|| USimpleError::new(1, "output file suffixes exhausted"))?; @@ -1528,14 +1077,7 @@ where .map_err(|_| USimpleError::new(1, "Number of chunks too big"))?; // This object is responsible for creating the filename for each chunk. - let mut filename_iterator = FilenameIterator::new( - &settings.prefix, - &settings.additional_suffix, - settings.suffix_length, - settings.suffix_type, - settings.suffix_start, - settings.suffix_auto_widening, - )?; + let mut filename_iterator = FilenameIterator::new(&settings.prefix, &settings.suffix)?; // Create one writer for each chunk. This will create each // of the underlying files (if not in `--filter` mode). @@ -1700,14 +1242,7 @@ where let chunk_size = (num_bytes / num_chunks) as usize; // This object is responsible for creating the filename for each chunk. - let mut filename_iterator = FilenameIterator::new( - &settings.prefix, - &settings.additional_suffix, - settings.suffix_length, - settings.suffix_type, - settings.suffix_start, - settings.suffix_auto_widening, - )?; + let mut filename_iterator = FilenameIterator::new(&settings.prefix, &settings.suffix)?; // Create one writer for each chunk. This will create each // of the underlying files (if not in `--filter` mode). @@ -1842,15 +1377,8 @@ where R: BufRead, { // This object is responsible for creating the filename for each chunk. - let mut filename_iterator = FilenameIterator::new( - &settings.prefix, - &settings.additional_suffix, - settings.suffix_length, - settings.suffix_type, - settings.suffix_start, - settings.suffix_auto_widening, - ) - .map_err(|e| io::Error::new(ErrorKind::Other, format!("{e}")))?; + let mut filename_iterator = FilenameIterator::new(&settings.prefix, &settings.suffix) + .map_err(|e| io::Error::new(ErrorKind::Other, format!("{e}")))?; // Create one writer for each chunk. This will create each // of the underlying files (if not in `--filter` mode). @@ -2027,101 +1555,3 @@ fn split(settings: &Settings) -> UResult<()> { } } } - -#[cfg(test)] -mod tests { - - use crate::NumberType; - use crate::NumberTypeError; - - #[test] - fn test_number_type_from() { - assert_eq!(NumberType::from("123").unwrap(), NumberType::Bytes(123)); - assert_eq!(NumberType::from("l/123").unwrap(), NumberType::Lines(123)); - assert_eq!( - NumberType::from("l/123/456").unwrap(), - NumberType::KthLines(123, 456) - ); - assert_eq!( - NumberType::from("r/123").unwrap(), - NumberType::RoundRobin(123) - ); - assert_eq!( - NumberType::from("r/123/456").unwrap(), - NumberType::KthRoundRobin(123, 456) - ); - } - - #[test] - #[allow(clippy::cognitive_complexity)] - fn test_number_type_from_error() { - assert_eq!( - NumberType::from("xyz").unwrap_err(), - NumberTypeError::NumberOfChunks("xyz".to_string()) - ); - assert_eq!( - NumberType::from("l/xyz").unwrap_err(), - NumberTypeError::NumberOfChunks("xyz".to_string()) - ); - assert_eq!( - NumberType::from("l/123/xyz").unwrap_err(), - NumberTypeError::NumberOfChunks("xyz".to_string()) - ); - assert_eq!( - NumberType::from("l/abc/456").unwrap_err(), - NumberTypeError::ChunkNumber("abc".to_string()) - ); - assert_eq!( - NumberType::from("l/456/123").unwrap_err(), - NumberTypeError::ChunkNumber("456".to_string()) - ); - assert_eq!( - NumberType::from("r/456/123").unwrap_err(), - NumberTypeError::ChunkNumber("456".to_string()) - ); - assert_eq!( - NumberType::from("456/123").unwrap_err(), - NumberTypeError::ChunkNumber("456".to_string()) - ); - // In GNU split, the number of chunks get precedence: - // - // $ split -n l/abc/xyz - // split: invalid number of chunks: ‘xyz’ - // - assert_eq!( - NumberType::from("l/abc/xyz").unwrap_err(), - NumberTypeError::NumberOfChunks("xyz".to_string()) - ); - assert_eq!( - NumberType::from("r/xyz").unwrap_err(), - NumberTypeError::NumberOfChunks("xyz".to_string()) - ); - assert_eq!( - NumberType::from("r/123/xyz").unwrap_err(), - NumberTypeError::NumberOfChunks("xyz".to_string()) - ); - assert_eq!( - NumberType::from("r/abc/456").unwrap_err(), - NumberTypeError::ChunkNumber("abc".to_string()) - ); - // In GNU split, the number of chunks get precedence: - // - // $ split -n r/abc/xyz - // split: invalid number of chunks: ‘xyz’ - // - assert_eq!( - NumberType::from("r/abc/xyz").unwrap_err(), - NumberTypeError::NumberOfChunks("xyz".to_string()) - ); - } - - #[test] - fn test_number_type_num_chunks() { - assert_eq!(NumberType::from("123").unwrap().num_chunks(), 123); - assert_eq!(NumberType::from("123/456").unwrap().num_chunks(), 456); - assert_eq!(NumberType::from("l/123").unwrap().num_chunks(), 123); - assert_eq!(NumberType::from("l/123/456").unwrap().num_chunks(), 456); - assert_eq!(NumberType::from("r/123").unwrap().num_chunks(), 123); - assert_eq!(NumberType::from("r/123/456").unwrap().num_chunks(), 456); - } -} diff --git a/src/uu/split/src/strategy.rs b/src/uu/split/src/strategy.rs new file mode 100644 index 00000000000..e85abcee58b --- /dev/null +++ b/src/uu/split/src/strategy.rs @@ -0,0 +1,379 @@ +// This file is part of the uutils coreutils package. +// +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. +//! Determine the strategy for breaking up the input (file or stdin) into chunks +//! based on the command line options + +use crate::{OPT_BYTES, OPT_LINES, OPT_LINE_BYTES, OPT_NUMBER}; +use clap::{parser::ValueSource, ArgMatches}; +use std::fmt; +use uucore::parse_size::{parse_size_u64, parse_size_u64_max, ParseSizeError}; + +/// Sub-strategy of the [`Strategy::Number`] +/// Splitting a file into a specific number of chunks. +#[derive(Debug, PartialEq)] +pub enum NumberType { + /// Split into a specific number of chunks by byte. + Bytes(u64), + + /// Split into a specific number of chunks by byte + /// but output only the *k*th chunk. + KthBytes(u64, u64), + + /// Split into a specific number of chunks by line (approximately). + Lines(u64), + + /// Split into a specific number of chunks by line + /// (approximately), but output only the *k*th chunk. + KthLines(u64, u64), + + /// Assign lines via round-robin to the specified number of output chunks. + RoundRobin(u64), + + /// Assign lines via round-robin to the specified number of output + /// chunks, but output only the *k*th chunk. + KthRoundRobin(u64, u64), +} + +impl NumberType { + /// The number of chunks for this number type. + pub fn num_chunks(&self) -> u64 { + match self { + Self::Bytes(n) => *n, + Self::KthBytes(_, n) => *n, + Self::Lines(n) => *n, + Self::KthLines(_, n) => *n, + Self::RoundRobin(n) => *n, + Self::KthRoundRobin(_, n) => *n, + } + } +} + +/// An error due to an invalid parameter to the `-n` command-line option. +#[derive(Debug, PartialEq)] +pub enum NumberTypeError { + /// The number of chunks was invalid. + /// + /// This can happen if the value of `N` in any of the following + /// command-line options is not a positive integer: + /// + /// ```ignore + /// -n N + /// -n K/N + /// -n l/N + /// -n l/K/N + /// -n r/N + /// -n r/K/N + /// ``` + NumberOfChunks(String), + + /// The chunk number was invalid. + /// + /// This can happen if the value of `K` in any of the following + /// command-line options is not a positive integer + /// or if `K` is 0 + /// or if `K` is greater than `N`: + /// + /// ```ignore + /// -n K/N + /// -n l/K/N + /// -n r/K/N + /// ``` + ChunkNumber(String), +} + +impl NumberType { + /// Parse a `NumberType` from a string. + /// + /// The following strings are valid arguments: + /// + /// ```ignore + /// "N" + /// "K/N" + /// "l/N" + /// "l/K/N" + /// "r/N" + /// "r/K/N" + /// ``` + /// + /// The `N` represents the number of chunks and the `K` represents + /// a chunk number. + /// + /// # Errors + /// + /// If the string is not one of the valid number types, + /// if `K` is not a nonnegative integer, + /// or if `K` is 0, + /// or if `N` is not a positive integer, + /// or if `K` is greater than `N` + /// then this function returns [`NumberTypeError`]. + fn from(s: &str) -> Result { + fn is_invalid_chunk(chunk_number: u64, num_chunks: u64) -> bool { + chunk_number > num_chunks || chunk_number == 0 + } + let parts: Vec<&str> = s.split('/').collect(); + match &parts[..] { + [n_str] => { + let num_chunks = parse_size_u64(n_str) + .map_err(|_| NumberTypeError::NumberOfChunks(n_str.to_string()))?; + if num_chunks > 0 { + Ok(Self::Bytes(num_chunks)) + } else { + Err(NumberTypeError::NumberOfChunks(s.to_string())) + } + } + [k_str, n_str] if !k_str.starts_with('l') && !k_str.starts_with('r') => { + let num_chunks = parse_size_u64(n_str) + .map_err(|_| NumberTypeError::NumberOfChunks(n_str.to_string()))?; + let chunk_number = parse_size_u64(k_str) + .map_err(|_| NumberTypeError::ChunkNumber(k_str.to_string()))?; + if is_invalid_chunk(chunk_number, num_chunks) { + return Err(NumberTypeError::ChunkNumber(k_str.to_string())); + } + Ok(Self::KthBytes(chunk_number, num_chunks)) + } + ["l", n_str] => { + let num_chunks = parse_size_u64(n_str) + .map_err(|_| NumberTypeError::NumberOfChunks(n_str.to_string()))?; + Ok(Self::Lines(num_chunks)) + } + ["l", k_str, n_str] => { + let num_chunks = parse_size_u64(n_str) + .map_err(|_| NumberTypeError::NumberOfChunks(n_str.to_string()))?; + let chunk_number = parse_size_u64(k_str) + .map_err(|_| NumberTypeError::ChunkNumber(k_str.to_string()))?; + if is_invalid_chunk(chunk_number, num_chunks) { + return Err(NumberTypeError::ChunkNumber(k_str.to_string())); + } + Ok(Self::KthLines(chunk_number, num_chunks)) + } + ["r", n_str] => { + let num_chunks = parse_size_u64(n_str) + .map_err(|_| NumberTypeError::NumberOfChunks(n_str.to_string()))?; + Ok(Self::RoundRobin(num_chunks)) + } + ["r", k_str, n_str] => { + let num_chunks = parse_size_u64(n_str) + .map_err(|_| NumberTypeError::NumberOfChunks(n_str.to_string()))?; + let chunk_number = parse_size_u64(k_str) + .map_err(|_| NumberTypeError::ChunkNumber(k_str.to_string()))?; + if is_invalid_chunk(chunk_number, num_chunks) { + return Err(NumberTypeError::ChunkNumber(k_str.to_string())); + } + Ok(Self::KthRoundRobin(chunk_number, num_chunks)) + } + _ => Err(NumberTypeError::NumberOfChunks(s.to_string())), + } + } +} + +/// The strategy for breaking up the input file into chunks. +pub enum Strategy { + /// Each chunk has the specified number of lines. + Lines(u64), + + /// Each chunk has the specified number of bytes. + Bytes(u64), + + /// Each chunk has as many lines as possible without exceeding the + /// specified number of bytes. + LineBytes(u64), + + /// Split the file into this many chunks. + /// + /// There are several sub-strategies available, as defined by + /// [`NumberType`]. + Number(NumberType), +} + +/// An error when parsing a chunking strategy from command-line arguments. +pub enum StrategyError { + /// Invalid number of lines. + Lines(ParseSizeError), + + /// Invalid number of bytes. + Bytes(ParseSizeError), + + /// Invalid number type. + NumberType(NumberTypeError), + + /// Multiple chunking strategies were specified (but only one should be). + MultipleWays, +} + +impl fmt::Display for StrategyError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::Lines(e) => write!(f, "invalid number of lines: {e}"), + Self::Bytes(e) => write!(f, "invalid number of bytes: {e}"), + Self::NumberType(NumberTypeError::NumberOfChunks(s)) => { + write!(f, "invalid number of chunks: {s}") + } + Self::NumberType(NumberTypeError::ChunkNumber(s)) => { + write!(f, "invalid chunk number: {s}") + } + Self::MultipleWays => write!(f, "cannot split in more than one way"), + } + } +} + +impl Strategy { + /// Parse a strategy from the command-line arguments. + pub fn from(matches: &ArgMatches, obs_lines: &Option) -> Result { + fn get_and_parse( + matches: &ArgMatches, + option: &str, + strategy: fn(u64) -> Strategy, + error: fn(ParseSizeError) -> StrategyError, + ) -> Result { + let s = matches.get_one::(option).unwrap(); + let n = parse_size_u64_max(s).map_err(error)?; + if n > 0 { + Ok(strategy(n)) + } else { + Err(error(ParseSizeError::ParseFailure(s.to_string()))) + } + } + // Check that the user is not specifying more than one strategy. + // + // Note: right now, this exact behavior cannot be handled by + // overrides_with_all() due to obsolete lines value option + match ( + obs_lines, + matches.value_source(OPT_LINES) == Some(ValueSource::CommandLine), + matches.value_source(OPT_BYTES) == Some(ValueSource::CommandLine), + matches.value_source(OPT_LINE_BYTES) == Some(ValueSource::CommandLine), + matches.value_source(OPT_NUMBER) == Some(ValueSource::CommandLine), + ) { + (Some(v), false, false, false, false) => { + let v = parse_size_u64_max(v).map_err(|_| { + StrategyError::Lines(ParseSizeError::ParseFailure(v.to_string())) + })?; + if v > 0 { + Ok(Self::Lines(v)) + } else { + Err(StrategyError::Lines(ParseSizeError::ParseFailure( + v.to_string(), + ))) + } + } + (None, false, false, false, false) => Ok(Self::Lines(1000)), + (None, true, false, false, false) => { + get_and_parse(matches, OPT_LINES, Self::Lines, StrategyError::Lines) + } + (None, false, true, false, false) => { + get_and_parse(matches, OPT_BYTES, Self::Bytes, StrategyError::Bytes) + } + (None, false, false, true, false) => get_and_parse( + matches, + OPT_LINE_BYTES, + Self::LineBytes, + StrategyError::Bytes, + ), + (None, false, false, false, true) => { + let s = matches.get_one::(OPT_NUMBER).unwrap(); + let number_type = NumberType::from(s).map_err(StrategyError::NumberType)?; + Ok(Self::Number(number_type)) + } + _ => Err(StrategyError::MultipleWays), + } + } +} + +#[cfg(test)] +mod tests { + + use crate::{strategy::NumberType, strategy::NumberTypeError}; + + #[test] + fn test_number_type_from() { + assert_eq!(NumberType::from("123").unwrap(), NumberType::Bytes(123)); + assert_eq!(NumberType::from("l/123").unwrap(), NumberType::Lines(123)); + assert_eq!( + NumberType::from("l/123/456").unwrap(), + NumberType::KthLines(123, 456) + ); + assert_eq!( + NumberType::from("r/123").unwrap(), + NumberType::RoundRobin(123) + ); + assert_eq!( + NumberType::from("r/123/456").unwrap(), + NumberType::KthRoundRobin(123, 456) + ); + } + + #[test] + #[allow(clippy::cognitive_complexity)] + fn test_number_type_from_error() { + assert_eq!( + NumberType::from("xyz").unwrap_err(), + NumberTypeError::NumberOfChunks("xyz".to_string()) + ); + assert_eq!( + NumberType::from("l/xyz").unwrap_err(), + NumberTypeError::NumberOfChunks("xyz".to_string()) + ); + assert_eq!( + NumberType::from("l/123/xyz").unwrap_err(), + NumberTypeError::NumberOfChunks("xyz".to_string()) + ); + assert_eq!( + NumberType::from("l/abc/456").unwrap_err(), + NumberTypeError::ChunkNumber("abc".to_string()) + ); + assert_eq!( + NumberType::from("l/456/123").unwrap_err(), + NumberTypeError::ChunkNumber("456".to_string()) + ); + assert_eq!( + NumberType::from("r/456/123").unwrap_err(), + NumberTypeError::ChunkNumber("456".to_string()) + ); + assert_eq!( + NumberType::from("456/123").unwrap_err(), + NumberTypeError::ChunkNumber("456".to_string()) + ); + // In GNU split, the number of chunks get precedence: + // + // $ split -n l/abc/xyz + // split: invalid number of chunks: ‘xyz’ + // + assert_eq!( + NumberType::from("l/abc/xyz").unwrap_err(), + NumberTypeError::NumberOfChunks("xyz".to_string()) + ); + assert_eq!( + NumberType::from("r/xyz").unwrap_err(), + NumberTypeError::NumberOfChunks("xyz".to_string()) + ); + assert_eq!( + NumberType::from("r/123/xyz").unwrap_err(), + NumberTypeError::NumberOfChunks("xyz".to_string()) + ); + assert_eq!( + NumberType::from("r/abc/456").unwrap_err(), + NumberTypeError::ChunkNumber("abc".to_string()) + ); + // In GNU split, the number of chunks get precedence: + // + // $ split -n r/abc/xyz + // split: invalid number of chunks: ‘xyz’ + // + assert_eq!( + NumberType::from("r/abc/xyz").unwrap_err(), + NumberTypeError::NumberOfChunks("xyz".to_string()) + ); + } + + #[test] + fn test_number_type_num_chunks() { + assert_eq!(NumberType::from("123").unwrap().num_chunks(), 123); + assert_eq!(NumberType::from("123/456").unwrap().num_chunks(), 456); + assert_eq!(NumberType::from("l/123").unwrap().num_chunks(), 123); + assert_eq!(NumberType::from("l/123/456").unwrap().num_chunks(), 456); + assert_eq!(NumberType::from("r/123").unwrap().num_chunks(), 123); + assert_eq!(NumberType::from("r/123/456").unwrap().num_chunks(), 456); + } +} From f05474a33ad4888708ff36ae4cd149763eb2e462 Mon Sep 17 00:00:00 2001 From: zhitkoff Date: Mon, 30 Oct 2023 15:39:29 -0400 Subject: [PATCH 0299/2851] split: slash separator --- src/uu/split/src/filenames.rs | 2 +- tests/by-util/test_split.rs | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/uu/split/src/filenames.rs b/src/uu/split/src/filenames.rs index e776b274b65..80243c2bc54 100644 --- a/src/uu/split/src/filenames.rs +++ b/src/uu/split/src/filenames.rs @@ -230,7 +230,7 @@ impl Suffix { .get_one::(OPT_ADDITIONAL_SUFFIX) .unwrap() .to_string(); - if additional.contains('/') { + if additional.contains('/') || additional.contains('\\') { return Err(SuffixError::ContainsSeparator(additional)); } diff --git a/tests/by-util/test_split.rs b/tests/by-util/test_split.rs index 3ebadde4d09..e0e85be4822 100644 --- a/tests/by-util/test_split.rs +++ b/tests/by-util/test_split.rs @@ -252,6 +252,10 @@ fn test_additional_suffix_no_slash() { .args(&["--additional-suffix", "a/b"]) .fails() .usage_error("invalid suffix 'a/b', contains directory separator"); + new_ucmd!() + .args(&["--additional-suffix", "a\\b"]) + .fails() + .usage_error("invalid suffix 'a\\b', contains directory separator"); } #[test] From 62887c7a58b0f5e4045a418aa2df09e9b2a54654 Mon Sep 17 00:00:00 2001 From: zhitkoff Date: Thu, 2 Nov 2023 10:36:15 -0400 Subject: [PATCH 0300/2851] split: directory separator in additional suffix --- src/uu/split/src/filenames.rs | 3 ++- tests/by-util/test_split.rs | 5 ++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/uu/split/src/filenames.rs b/src/uu/split/src/filenames.rs index 80243c2bc54..843e11ea014 100644 --- a/src/uu/split/src/filenames.rs +++ b/src/uu/split/src/filenames.rs @@ -40,6 +40,7 @@ use crate::{ }; use clap::ArgMatches; use std::fmt; +use std::path::is_separator; use uucore::display::Quotable; use uucore::error::{UResult, USimpleError}; @@ -230,7 +231,7 @@ impl Suffix { .get_one::(OPT_ADDITIONAL_SUFFIX) .unwrap() .to_string(); - if additional.contains('/') || additional.contains('\\') { + if additional.chars().any(is_separator) { return Err(SuffixError::ContainsSeparator(additional)); } diff --git a/tests/by-util/test_split.rs b/tests/by-util/test_split.rs index e0e85be4822..aec6f059441 100644 --- a/tests/by-util/test_split.rs +++ b/tests/by-util/test_split.rs @@ -247,11 +247,14 @@ fn test_split_additional_suffix() { } #[test] -fn test_additional_suffix_no_slash() { +fn test_additional_suffix_dir_separator() { + #[cfg(unix)] new_ucmd!() .args(&["--additional-suffix", "a/b"]) .fails() .usage_error("invalid suffix 'a/b', contains directory separator"); + + #[cfg(windows)] new_ucmd!() .args(&["--additional-suffix", "a\\b"]) .fails() From 3d0c8ae6e72ac53b50e8de583e7acdf1b59b037c Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Thu, 2 Nov 2023 18:19:56 +0100 Subject: [PATCH 0301/2851] Expand CONTRIBUTING.md (WIP) --- .../workspace.wordlist.txt | 1 + CONTRIBUTING.md | 267 ++++++++++++++---- 2 files changed, 211 insertions(+), 57 deletions(-) diff --git a/.vscode/cspell.dictionaries/workspace.wordlist.txt b/.vscode/cspell.dictionaries/workspace.wordlist.txt index 6d6533bcf5c..c3c854a4cd5 100644 --- a/.vscode/cspell.dictionaries/workspace.wordlist.txt +++ b/.vscode/cspell.dictionaries/workspace.wordlist.txt @@ -59,6 +59,7 @@ clippy rustc rustfmt rustup +rustdoc # bitor # BitOr trait function bitxor # BitXor trait function diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 695e5ad18ef..d9ee528f8fe 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,52 +1,202 @@ - + # Contributing to coreutils -Contributions are very welcome via Pull Requests. If you don't know where to -start, take a look at the -[`good-first-issues`](https://github.com/uutils/coreutils/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22). -If you have any questions, feel free to ask them in the issues or on -[Discord](https://discord.gg/wQVJbvJ). - -## Best practices - -1. Follow what GNU is doing in terms of options and behavior. It is recommended - to look at the GNU Coreutils manual ([on the - web](https://www.gnu.org/software/coreutils/manual/html_node/index.html), or - locally using `info `). It is more in depth than the man pages and - provides a good description of available features and their implementation - details. -1. If possible, look at the GNU test suite execution in the CI and make the test - work if failing. -1. Use clap for argument management. -1. Make sure that the code coverage is covering all of the cases, including - errors. -1. The code must be clippy-warning-free and rustfmt-compliant. -1. Don't hesitate to move common functions into uucore if they can be reused by - other binaries. -1. Unsafe code should be documented with Safety comments. -1. uutils is original code. It cannot contain code from existing GNU or Unix-like - utilities, nor should it link to or reference GNU libraries. +Hi! Welcome to uutils/coreutils! + +Thanks for wanting to contribute to this project! This document explains +everything you need to know to contribute. Before you start make sure to also +check out these documents: + +- Our community's [CODE_OF_CONDUCT.md](./CODE_OF_CONDUCT.md). +- [DEVELOPMENT.md](./DEVELOPMENT.md) for setting up your development + environment. + +Now follows a very important warning: + +> [!WARNING] +> uutils is original code and cannot contain any code from GNU or +> other implementations. This means that **we cannot accept any changes based on +> the GNU source code**. To make sure that cannot happen, **you cannot link to +> the GNU source code** either. + +Finally, feel free to join our [Discord](https://discord.gg/wQVJbvJ)! + +## Getting Oriented + +uutils is a big project consisting of many parts. Here are the most important +parts for getting started: + +- [`src/uu`](./src/uu/): The code for all utilities +- [`src/uucore`](./src/uucore/): Crate containing all the shared code between + the utilities. +- [`tests/by-util`](./tests/by-util/): The tests for all utilities. +- [`src/bin/coreutils.rs`](./src/bin/coreutils.rs): Code for the multicall + binary. +- [`docs`](./docs/src): the documentation for the website + +Each utility is defined as a separate crate. The structure of each of these +crates is as follows: + +- `Cargo.toml` +- `src/main.rs`: contains only a single macro call +- `src/.rs`: the actual code for the utility +- `.md`: the documentation for the utility + +We have separated repositories for crates that we maintain but also publish for +use by others: + +- [uutils-term-grid](https://github.com/uutils/uutils-term-grid) +- [parse_datetime](https://github.com/uutils/parse_datetime) + +## Design Goals + +todo + +## How to Help + +There are several ways to help and writing code is just one them. Reporting +issues and writing documentation are just as important as writing code. + +### Reporting Issues + +We can't fix bugs we don't know about, so good issues are super helpful! Here +are some tips for writing good issues: + +- If you find a bug, make sure it's still a problem on the `main` branch. +- Search through the existing issues to see whether it has already been + reported. +- Make sure to include all relevant information, such as: + - Which version of uutils did you check? + - Which version of GNU coreutils are you comparing with? + - What platform are you on? +- Provide a way to reliably reproduce the issue. +- Be as specific as possible! + +### Writing Documentation + +There's never enough documentation. If you come across any documentation that +could be improved, feel free to submit a PR for it! + +### Writing Code + +If you want to submit a PR, make sure that you've discussed the solution with +the maintainers beforehand. We want to avoid situations where you put a lot of +work into a fix that we can't merge! If there's no issue for what you're trying +to fix yet, make one _before_ you start working on the PR. + +Generally, we try to follow what GNU is doing in terms of options and behavior. +It is recommended to look at the GNU Coreutils manual +([on the web](https://www.gnu.org/software/coreutils/manual/html_node/index.html), +or locally using `info `). It is more in depth than the man pages and +provides a good description of available features and their implementation +details. But remember, you cannot look at the GNU source code! + +Also remember that we can only merge PRs which pass our test suite, follow +rustfmt, and do not have any warnings from clippy. See +[DEVELOPMENT.md](./DEVELOPMENT.md) for more information. Be sure to also read +about our [Rust style](#our-rust-style). + +## Our Rust Style + +We want uutils to be written in idiomatic Rust, so here are some guidelines to +follow. Some of these are aspirational, meaning that we don't do them correctly +everywhere in the code. If you find violations of the advice below, feel free to +submit a patch! + +### Don't `panic!` + +The coreutils should be very reliable. This means that we should never `panic!`. +Therefore, you should avoid using `.unwrap()` and `panic!`. Sometimes the use of +`unreachable!` can be justified with a comment explaining why that code is +unreachable. + +### Don't `exit` + +We want uutils to be embeddable in other programs. This means that no function +in uutils should exit the program. Doing so would also lead to code with more +confusing control flow. Avoid therefore `std::process::exit` and similar +functions which exit the program early. + +### `unsafe` + +uutils cannot be entirely safe, because we have to call out to `libc` and do +syscalls. However, we still want to limit our use of `unsafe`. We generally only +accept `unsafe` for FFI, with very few exceptions. Note that performance is very +rarely a valid argument for using `unsafe`. + +If you still need to write code with `unsafe`, make sure to read the +[Rustonomicon](https://doc.rust-lang.org/nomicon/intro.html) and annotate the +calls with `// SAFETY:` comments explaining why the use of `unsafe` is sound. + +### Macros + +Macros can be a great tool, but they are also usually hard to understand. They +should be used sparingly. Make sure to explore simpler options before you reach +for a solution involving macros. + +### `str`, `OsStr` & `Path` + +Rust has many string-like types, and sometimes it's hard to choose the right +one. It's tempting to use `str` (and `String`) for everything, but that is not +always the right choice for uutils, because we need to support invalid UTF-8, +just like the GNU coreutils. For example, paths on Linux might not be valid +UTF-8! Whenever we are dealing with paths, we should therefore stick with +`OsStr` and `Path`. Make sure that you only convert to `str`/`String` if you +know that something is always valid UTF-8. If you need more operations on +`OsStr`, you can use the [`bstr`](https://docs.rs/bstr/latest/bstr/) crate. + +### Doc-comments + +We use rustdoc for our documentation, so it's best to follow +[rustdoc's guidelines](https://doc.rust-lang.org/rustdoc/how-to-write-documentation.html#documenting-components). +Make sure that your documentation is not just repeating the name of the +function, but actually giving more useful information. Rustdoc recommends the +following structure: + +``` +[short sentence explaining what it is] + +[more detailed explanation] + +[at least one code example that users can copy/paste to try it] + +[even more advanced explanations if necessary] +``` + +### Other comments + +Comments should be written to _explain_ the code, not to _describe_ the code. +Try to focus on explaining _why_ the code is the way it is. If you feel like you +have to describe the code, that's usually a sign that you could improve the +naming of variables and functions. + +If you edit a piece of code, make sure to update any comments that need to +change as a result. The only thing worse than having no comments is having +outdated comments! + +## Git Etiquette + +todo ## Platforms We take pride in supporting many operating systems and architectures. Any code you contribute must at least compile without warnings for all platforms in the -CI. However, you can use `#[cfg(...)]` attributes to create platform dependent features. +CI. However, you can use `#[cfg(...)]` attributes to create platform dependent +features. **Tip:** For Windows, Microsoft provides some images (VMWare, Hyper-V, VirtualBox and Parallels) for development: -## Setting up your development environment - -To setup your local development environment for this project please follow [DEVELOPMENT.md guide](DEVELOPMENT.md) - -It covers [installation of necessary tools and prerequisites](DEVELOPMENT.md#tools) as well as using those tools to [test your code changes locally](DEVELOPMENT.md#testing) - ## Improving the GNU compatibility -Please make sure you have installed [GNU utils and prerequisites](DEVELOPMENT.md#gnu-utils-and-prerequisites) and can execute commands described in [Comparing with GNU](DEVELOPMENT.md#comparing-with-gnu) section of [DEVELOPMENT.md](DEVELOPMENT.md) +Please make sure you have installed +[GNU utils and prerequisites](DEVELOPMENT.md#gnu-utils-and-prerequisites) and +can execute commands described in +[Comparing with GNU](DEVELOPMENT.md#comparing-with-gnu) section of +[DEVELOPMENT.md](DEVELOPMENT.md) The Python script `./util/remaining-gnu-error.py` shows the list of failing tests in the CI. @@ -103,20 +253,20 @@ Further paragraphs come after blank lines. Furthermore, here are a few examples for a summary line: -* commit for a single utility +- commit for a single utility ``` nohup: cleanup and refactor ``` -* commit for a utility's tests +- commit for a utility's tests ``` tests/rm: test new feature ``` -Beyond changes to an individual utility or its tests, other summary -lines for non-utility modules include: +Beyond changes to an individual utility or its tests, other summary lines for +non-utility modules include: ``` README: add help @@ -136,23 +286,26 @@ gitignore: add temporary files ## Code coverage -To generate code coverage report locally please follow [Code coverage report](DEVELOPMENT.md#code-coverage-report) section of [DEVELOPMENT.md](DEVELOPMENT.md) +To generate code coverage report locally please follow +[Code coverage report](DEVELOPMENT.md#code-coverage-report) section of +[DEVELOPMENT.md](DEVELOPMENT.md) ## Other implementations -The Coreutils have different implementations, with different levels of completions: +The Coreutils have different implementations, with different levels of +completions: -* [GNU's](https://git.savannah.gnu.org/gitweb/?p=coreutils.git) -* [OpenBSD](https://github.com/openbsd/src/tree/master/bin) -* [Busybox](https://github.com/mirror/busybox/tree/master/coreutils) -* [Toybox (Android)](https://github.com/landley/toybox/tree/master/toys/posix) -* [V lang](https://github.com/vlang/coreutils) -* [SerenityOS](https://github.com/SerenityOS/serenity/tree/master/Userland/Utilities) -* [Initial Unix](https://github.com/dspinellis/unix-history-repo) -* [Perl Power Tools](https://metacpan.org/pod/PerlPowerTools) +- [GNU's](https://git.savannah.gnu.org/gitweb/?p=coreutils.git) +- [OpenBSD](https://github.com/openbsd/src/tree/master/bin) +- [Busybox](https://github.com/mirror/busybox/tree/master/coreutils) +- [Toybox (Android)](https://github.com/landley/toybox/tree/master/toys/posix) +- [V lang](https://github.com/vlang/coreutils) +- [SerenityOS](https://github.com/SerenityOS/serenity/tree/master/Userland/Utilities) +- [Initial Unix](https://github.com/dspinellis/unix-history-repo) +- [Perl Power Tools](https://metacpan.org/pod/PerlPowerTools) -However, when reimplementing the tools/options in Rust, don't read their source codes -when they are using reciprocal licenses (ex: GNU GPL, GNU LGPL, etc). +However, when reimplementing the tools/options in Rust, don't read their source +codes when they are using reciprocal licenses (ex: GNU GPL, GNU LGPL, etc). ## Licensing @@ -167,17 +320,17 @@ If you wish to add or change dependencies as part of a contribution to the project, a tool like `cargo-license` can be used to show their license details. The following types of license are acceptable: -* MIT License -* Dual- or tri-license with an MIT License option ("Apache-2.0 or MIT" is a +- MIT License +- Dual- or tri-license with an MIT License option ("Apache-2.0 or MIT" is a popular combination) -* "MIT equivalent" license (2-clause BSD, 3-clause BSD, ISC) -* License less restrictive than the MIT License (CC0 1.0 Universal) -* Apache License version 2.0 +- "MIT equivalent" license (2-clause BSD, 3-clause BSD, ISC) +- License less restrictive than the MIT License (CC0 1.0 Universal) +- Apache License version 2.0 Licenses we will not use: -* An ambiguous license, or no license -* Strongly reciprocal licenses (GNU GPL, GNU LGPL) +- An ambiguous license, or no license +- Strongly reciprocal licenses (GNU GPL, GNU LGPL) If you wish to add a reference but it doesn't meet these requirements, please raise an issue to describe the dependency. From d7b256be7bbbbc91ff95ef3e7805d5733fca7382 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Fri, 3 Nov 2023 11:44:19 +0100 Subject: [PATCH 0302/2851] CONTRIBUTING.md: write Design Goals section --- CONTRIBUTING.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d9ee528f8fe..f59d3b4f87f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -51,7 +51,13 @@ use by others: ## Design Goals -todo +We have the following goals with our development: + +- **Compatible**: The utilities should be a drop-in replacement for the GNU coreutils. +- **Cross-platform**: All utilities should run on as many of the supported platforms as possible. +- **Reliable**: The utilities should never unexpectedly fail. +- **Performant**: Our utilities should be written in fast idiomatic Rust. We aim to match or exceed the performance of the GNU utilities. +- **Well-tested**: We should have a lot of tests to be able to guarantee reliability and compatibility. ## How to Help From 29f6631554eb6417f4f302d113078e7de43b95b7 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Fri, 3 Nov 2023 16:49:19 +0100 Subject: [PATCH 0303/2851] du: add -P/--no-dereference --- src/uu/du/src/du.rs | 16 +++++++++------- tests/by-util/test_du.rs | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 7 deletions(-) diff --git a/src/uu/du/src/du.rs b/src/uu/du/src/du.rs index ad5e87833ef..ae88ed13bd1 100644 --- a/src/uu/du/src/du.rs +++ b/src/uu/du/src/du.rs @@ -68,6 +68,7 @@ mod options { pub const ONE_FILE_SYSTEM: &str = "one-file-system"; pub const DEREFERENCE: &str = "dereference"; pub const DEREFERENCE_ARGS: &str = "dereference-args"; + pub const NO_DEREFERENCE: &str = "no-dereference"; pub const INODES: &str = "inodes"; pub const EXCLUDE: &str = "exclude"; pub const EXCLUDE_FROM: &str = "exclude-from"; @@ -824,13 +825,14 @@ pub fn uu_app() -> Command { .help("follow only symlinks that are listed on the command line") .action(ArgAction::SetTrue) ) - // .arg( - // Arg::new("no-dereference") - // .short('P') - // .long("no-dereference") - // .help("don't follow any symbolic links (this is the default)") - // .action(ArgAction::SetTrue), - // ) + .arg( + Arg::new(options::NO_DEREFERENCE) + .short('P') + .long(options::NO_DEREFERENCE) + .help("don't follow any symbolic links (this is the default)") + .overrides_with(options::DEREFERENCE) + .action(ArgAction::SetTrue), + ) .arg( Arg::new(options::BLOCK_SIZE_1M) .short('m') diff --git a/tests/by-util/test_du.rs b/tests/by-util/test_du.rs index 9a508da255e..7e6bc47756a 100644 --- a/tests/by-util/test_du.rs +++ b/tests/by-util/test_du.rs @@ -336,6 +336,42 @@ fn _du_dereference(s: &str) { } } +#[cfg(not(windows))] +#[test] +fn test_du_no_dereference() { + let ts = TestScenario::new(util_name!()); + let at = &ts.fixtures; + let dir = "a_dir"; + let symlink = "symlink"; + + at.mkdir(dir); + at.symlink_dir(dir, symlink); + + for arg in ["-P", "--no-dereference"] { + ts.ucmd() + .arg(arg) + .succeeds() + .stdout_contains(dir) + .stdout_does_not_contain(symlink); + + // ensure no-dereference "wins" + ts.ucmd() + .arg("--dereference") + .arg(arg) + .succeeds() + .stdout_contains(dir) + .stdout_does_not_contain(symlink); + + // ensure dereference "wins" + ts.ucmd() + .arg(arg) + .arg("--dereference") + .succeeds() + .stdout_contains(symlink) + .stdout_does_not_contain(dir); + } +} + #[test] fn test_du_inodes_basic() { let ts = TestScenario::new(util_name!()); From 44d105d01533e1a0dc255ec80718c739e4ad0b87 Mon Sep 17 00:00:00 2001 From: Brandon Elam Barker Date: Sat, 4 Nov 2023 07:31:11 -0400 Subject: [PATCH 0304/2851] Add support in uucore for illumos and solaris (#5489) * uucore support for illumos and solaris * use macro to consolidate illumos and solaris signals * fixing some CI issues * replaced macro with better cfg usage --- src/uucore/src/lib/features/fs.rs | 4 + src/uucore/src/lib/features/fsext.rs | 13 ++- src/uucore/src/lib/features/signals.rs | 116 ++++++++++++++++++++++++- 3 files changed, 129 insertions(+), 4 deletions(-) diff --git a/src/uucore/src/lib/features/fs.rs b/src/uucore/src/lib/features/fs.rs index 84ed006d975..f8593dfede5 100644 --- a/src/uucore/src/lib/features/fs.rs +++ b/src/uucore/src/lib/features/fs.rs @@ -115,6 +115,8 @@ impl FileInformation { not(target_os = "android"), not(target_os = "freebsd"), not(target_os = "netbsd"), + not(target_os = "illumos"), + not(target_os = "solaris"), not(target_arch = "aarch64"), not(target_arch = "riscv64"), target_pointer_width = "64" @@ -127,6 +129,8 @@ impl FileInformation { target_os = "android", target_os = "freebsd", target_os = "netbsd", + target_os = "illumos", + target_os = "solaris", target_arch = "aarch64", target_arch = "riscv64", not(target_pointer_width = "64") diff --git a/src/uucore/src/lib/features/fsext.rs b/src/uucore/src/lib/features/fsext.rs index 5c2121d691c..8b1c42de6d1 100644 --- a/src/uucore/src/lib/features/fsext.rs +++ b/src/uucore/src/lib/features/fsext.rs @@ -69,6 +69,8 @@ use std::convert::{AsRef, From}; target_os = "openbsd", target_os = "linux", target_os = "android", + target_os = "illumos", + target_os = "solaris", ))] use std::ffi::CStr; #[cfg(not(windows))] @@ -309,7 +311,7 @@ impl MountInfo { target_os = "freebsd", target_vendor = "apple", target_os = "netbsd", - target_os = "openbsd" + target_os = "openbsd", ))] impl From for MountInfo { fn from(statfs: StatFs) -> Self { @@ -615,6 +617,8 @@ impl FsMeta for StatFs { not(target_vendor = "apple"), not(target_os = "android"), not(target_os = "freebsd"), + not(target_os = "illumos"), + not(target_os = "solaris"), not(target_arch = "s390x"), target_pointer_width = "64" ))] @@ -630,7 +634,12 @@ impl FsMeta for StatFs { ) ))] return self.f_bsize.into(); - #[cfg(any(target_env = "musl", target_os = "freebsd"))] + #[cfg(any( + target_env = "musl", + target_os = "freebsd", + target_os = "illumos", + target_os = "solaris" + ))] return self.f_bsize.try_into().unwrap(); } fn total_blocks(&self) -> u64 { diff --git a/src/uucore/src/lib/features/signals.rs b/src/uucore/src/lib/features/signals.rs index 61482024da0..2e8c26a4598 100644 --- a/src/uucore/src/lib/features/signals.rs +++ b/src/uucore/src/lib/features/signals.rs @@ -3,14 +3,15 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// spell-checker:ignore (vars/api) fcntl setrlimit setitimer -// spell-checker:ignore (vars/signals) ABRT ALRM CHLD SEGV SIGABRT SIGALRM SIGBUS SIGCHLD SIGCONT SIGEMT SIGFPE SIGHUP SIGILL SIGINFO SIGINT SIGIO SIGIOT SIGKILL SIGPIPE SIGPROF SIGPWR SIGQUIT SIGSEGV SIGSTOP SIGSYS SIGTERM SIGTRAP SIGTSTP SIGTHR SIGTTIN SIGTTOU SIGURG SIGUSR SIGVTALRM SIGWINCH SIGXCPU SIGXFSZ STKFLT PWR THR TSTP TTIN TTOU VTALRM XCPU XFSZ +// spell-checker:ignore (vars/api) fcntl setrlimit setitimer rubout pollable occured sysconf +// spell-checker:ignore (vars/signals) ABRT ALRM CHLD SEGV SIGABRT SIGALRM SIGBUS SIGCHLD SIGCONT SIGEMT SIGFPE SIGHUP SIGILL SIGINFO SIGINT SIGIO SIGIOT SIGKILL SIGPIPE SIGPROF SIGPWR SIGQUIT SIGSEGV SIGSTOP SIGSYS SIGTERM SIGTRAP SIGTSTP SIGTHR SIGTTIN SIGTTOU SIGURG SIGUSR SIGVTALRM SIGWINCH SIGXCPU SIGXFSZ STKFLT PWR THR TSTP TTIN TTOU VTALRM XCPU XFSZ SIGCLD SIGPOLL SIGWAITING SIGAIOCANCEL SIGLWP SIGFREEZE SIGTHAW SIGCANCEL SIGLOST SIGXRES SIGJVM SIGRTMIN SIGRT SIGRTMAX AIOCANCEL XRES RTMIN RTMAX #[cfg(unix)] use nix::errno::Errno; #[cfg(unix)] use nix::sys::signal::{ signal, SigHandler::SigDfl, SigHandler::SigIgn, Signal::SIGINT, Signal::SIGPIPE, }; + pub static DEFAULT_SIGNAL: usize = 15; /* @@ -178,6 +179,117 @@ pub static ALL_SIGNALS: [&str; 33] = [ "XCPU", "XFSZ", "VTALRM", "PROF", "WINCH", "INFO", "USR1", "USR2", "THR", ]; +/* + The following signals are defined in Solaris and illumos; + (the signals for illumos are the same as Solaris, but illumos still has SIGLWP + as well as the alias for SIGLWP (SIGAIOCANCEL)): + + SIGHUP 1 hangup + SIGINT 2 interrupt (rubout) + SIGQUIT 3 quit (ASCII FS) + SIGILL 4 illegal instruction (not reset when caught) + SIGTRAP 5 trace trap (not reset when caught) + SIGIOT 6 IOT instruction + SIGABRT 6 used by abort, replace SIGIOT in the future + SIGEMT 7 EMT instruction + SIGFPE 8 floating point exception + SIGKILL 9 kill (cannot be caught or ignored) + SIGBUS 10 bus error + SIGSEGV 11 segmentation violation + SIGSYS 12 bad argument to system call + SIGPIPE 13 write on a pipe with no one to read it + SIGALRM 14 alarm clock + SIGTERM 15 software termination signal from kill + SIGUSR1 16 user defined signal 1 + SIGUSR2 17 user defined signal 2 + SIGCLD 18 child status change + SIGCHLD 18 child status change alias (POSIX) + SIGPWR 19 power-fail restart + SIGWINCH 20 window size change + SIGURG 21 urgent socket condition + SIGPOLL 22 pollable event occured + SIGIO SIGPOLL socket I/O possible (SIGPOLL alias) + SIGSTOP 23 stop (cannot be caught or ignored) + SIGTSTP 24 user stop requested from tty + SIGCONT 25 stopped process has been continued + SIGTTIN 26 background tty read attempted + SIGTTOU 27 background tty write attempted + SIGVTALRM 28 virtual timer expired + SIGPROF 29 profiling timer expired + SIGXCPU 30 exceeded cpu limit + SIGXFSZ 31 exceeded file size limit + SIGWAITING 32 reserved signal no longer used by threading code + SIGAIOCANCEL 33 reserved signal no longer used by threading code (formerly SIGLWP) + SIGFREEZE 34 special signal used by CPR + SIGTHAW 35 special signal used by CPR + SIGCANCEL 36 reserved signal for thread cancellation + SIGLOST 37 resource lost (eg, record-lock lost) + SIGXRES 38 resource control exceeded + SIGJVM1 39 reserved signal for Java Virtual Machine + SIGJVM2 40 reserved signal for Java Virtual Machine + SIGINFO 41 information request + SIGRTMIN ((int)_sysconf(_SC_SIGRT_MIN)) first realtime signal + SIGRTMAX ((int)_sysconf(_SC_SIGRT_MAX)) last realtime signal +*/ + +#[cfg(target_os = "solaris")] +const SIGNALS_SIZE: usize = 46; + +#[cfg(target_os = "illumos")] +const SIGNALS_SIZE: usize = 47; + +#[cfg(any(target_os = "solaris", target_os = "illumos"))] +static ALL_SIGNALS: [&str; SIGNALS_SIZE] = [ + "HUP", + "INT", + "QUIT", + "ILL", + "TRAP", + "IOT", + "ABRT", + "EMT", + "FPE", + "KILL", + "BUS", + "SEGV", + "SYS", + "PIPE", + "ALRM", + "TERM", + "USR1", + "USR2", + "CLD", + "CHLD", + "PWR", + "WINCH", + "URG", + "POLL", + "IO", + "STOP", + "TSTP", + "CONT", + "TTIN", + "TTOU", + "VTALRM", + "PROF", + "XCPU", + "XFSZ", + "WAITING", + "AIOCANCEL", + #[cfg(target_os = "illumos")] + "LWP", + "FREEZE", + "THAW", + "CANCEL", + "LOST", + "XRES", + "JVM1", + "JVM2", + "INFO", + "RTMIN", + "RTMAX", +]; + pub fn signal_by_name_or_value(signal_name_or_value: &str) -> Option { if let Ok(value) = signal_name_or_value.parse() { if is_signal(value) { From 6ac1af69537e6eb5baa94c7bb46bf001fa44196a Mon Sep 17 00:00:00 2001 From: Kostiantyn Hryshchuk Date: Sat, 4 Nov 2023 20:00:53 +0100 Subject: [PATCH 0305/2851] Fix clippy::implicit_clone --- .github/workflows/code-quality.yml | 2 +- src/uu/base32/src/base_common.rs | 2 +- src/uu/cat/src/cat.rs | 2 +- src/uu/csplit/src/csplit.rs | 12 +++--------- src/uu/cut/src/cut.rs | 4 ++-- src/uu/dirname/src/dirname.rs | 2 +- src/uu/du/src/du.rs | 2 +- src/uu/fold/src/fold.rs | 4 ++-- src/uu/head/src/head.rs | 2 +- src/uu/install/src/install.rs | 17 ++++++----------- src/uu/ls/src/ls.rs | 18 +++++------------- src/uu/mkfifo/src/mkfifo.rs | 2 +- src/uu/mv/src/mv.rs | 2 +- src/uu/nl/src/helper.rs | 4 ++-- src/uu/nl/src/nl.rs | 2 +- src/uu/numfmt/src/numfmt.rs | 4 +--- src/uu/paste/src/paste.rs | 2 +- src/uu/ptx/src/ptx.rs | 2 +- src/uu/shuf/src/shuf.rs | 2 +- src/uu/sort/src/numeric_str_cmp.rs | 4 ++-- src/uu/split/src/split.rs | 6 +++--- src/uu/sum/src/sum.rs | 2 +- src/uu/tail/src/follow/files.rs | 2 +- src/uu/tail/src/follow/watch.rs | 10 +++++----- src/uu/test/src/parser.rs | 2 +- 25 files changed, 46 insertions(+), 67 deletions(-) diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml index 0a619e09773..98691f34bc8 100644 --- a/.github/workflows/code-quality.yml +++ b/.github/workflows/code-quality.yml @@ -117,7 +117,7 @@ jobs: run: | ## `cargo clippy` lint testing unset fault - CLIPPY_FLAGS="-W clippy::default_trait_access -W clippy::manual_string_new -W clippy::cognitive_complexity" + CLIPPY_FLAGS="-W clippy::default_trait_access -W clippy::manual_string_new -W clippy::cognitive_complexity -W clippy::implicit_clone" fault_type="${{ steps.vars.outputs.FAULT_TYPE }}" fault_prefix=$(echo "$fault_type" | tr '[:lower:]' '[:upper:]') # * convert any warnings to GHA UI annotations; ref: diff --git a/src/uu/base32/src/base_common.rs b/src/uu/base32/src/base_common.rs index 4a30705afcd..74c3dc80879 100644 --- a/src/uu/base32/src/base_common.rs +++ b/src/uu/base32/src/base_common.rs @@ -54,7 +54,7 @@ impl Config { format!("{}: No such file or directory", name.maybe_quote()), )); } - Some(name.to_owned()) + Some(name.clone()) } } None => None, diff --git a/src/uu/cat/src/cat.rs b/src/uu/cat/src/cat.rs index a7a4c5f406e..34eb265129d 100644 --- a/src/uu/cat/src/cat.rs +++ b/src/uu/cat/src/cat.rs @@ -213,7 +213,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let squeeze_blank = matches.get_flag(options::SQUEEZE_BLANK); let files: Vec = match matches.get_many::(options::FILE) { - Some(v) => v.clone().map(|v| v.to_owned()).collect(), + Some(v) => v.cloned().collect(), None => vec!["-".to_owned()], }; diff --git a/src/uu/csplit/src/csplit.rs b/src/uu/csplit/src/csplit.rs index 6e03c2e5c83..d33be1a5d54 100644 --- a/src/uu/csplit/src/csplit.rs +++ b/src/uu/csplit/src/csplit.rs @@ -62,15 +62,9 @@ impl CsplitOptions { split_name: crash_if_err!( 1, SplitName::new( - matches - .get_one::(options::PREFIX) - .map(|s| s.to_owned()), - matches - .get_one::(options::SUFFIX_FORMAT) - .map(|s| s.to_owned()), - matches - .get_one::(options::DIGITS) - .map(|s| s.to_owned()) + matches.get_one::(options::PREFIX).cloned(), + matches.get_one::(options::SUFFIX_FORMAT).cloned(), + matches.get_one::(options::DIGITS).cloned() ) ), keep_files, diff --git a/src/uu/cut/src/cut.rs b/src/uu/cut/src/cut.rs index 4d3145c059c..05e8bc6e424 100644 --- a/src/uu/cut/src/cut.rs +++ b/src/uu/cut/src/cut.rs @@ -400,7 +400,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { if s.is_empty() { Some("\0".to_owned()) } else { - Some(s.to_owned()) + Some(s.clone()) } } None => None, @@ -491,7 +491,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let files: Vec = matches .get_many::(options::FILE) .unwrap_or_default() - .map(|s| s.to_owned()) + .cloned() .collect(); match mode_parse { diff --git a/src/uu/dirname/src/dirname.rs b/src/uu/dirname/src/dirname.rs index 9a56e9f5bd6..51935cb7f23 100644 --- a/src/uu/dirname/src/dirname.rs +++ b/src/uu/dirname/src/dirname.rs @@ -30,7 +30,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let dirnames: Vec = matches .get_many::(options::DIR) .unwrap_or_default() - .map(|s| s.to_owned()) + .cloned() .collect(); if dirnames.is_empty() { diff --git a/src/uu/du/src/du.rs b/src/uu/du/src/du.rs index ae88ed13bd1..9139c31c313 100644 --- a/src/uu/du/src/du.rs +++ b/src/uu/du/src/du.rs @@ -506,7 +506,7 @@ fn build_exclude_patterns(matches: &ArgMatches) -> UResult> { let excludes_iterator = matches .get_many::(options::EXCLUDE) .unwrap_or_default() - .map(|v| v.to_owned()); + .cloned(); let mut exclude_patterns = Vec::new(); for f in excludes_iterator.chain(exclude_from_iterator) { diff --git a/src/uu/fold/src/fold.rs b/src/uu/fold/src/fold.rs index 95b6d9a8236..0223248be27 100644 --- a/src/uu/fold/src/fold.rs +++ b/src/uu/fold/src/fold.rs @@ -35,7 +35,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let bytes = matches.get_flag(options::BYTES); let spaces = matches.get_flag(options::SPACES); let poss_width = match matches.get_one::(options::WIDTH) { - Some(v) => Some(v.to_owned()), + Some(v) => Some(v.clone()), None => obs_width, }; @@ -50,7 +50,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { }; let files = match matches.get_many::(options::FILE) { - Some(v) => v.map(|v| v.to_owned()).collect(), + Some(v) => v.cloned().collect(), None => vec!["-".to_owned()], }; diff --git a/src/uu/head/src/head.rs b/src/uu/head/src/head.rs index c533f5a5df0..5d0d3beddc9 100644 --- a/src/uu/head/src/head.rs +++ b/src/uu/head/src/head.rs @@ -205,7 +205,7 @@ impl HeadOptions { options.mode = Mode::from(matches)?; options.files = match matches.get_many::(options::FILES_NAME) { - Some(v) => v.map(|s| s.to_owned()).collect(), + Some(v) => v.cloned().collect(), None => vec!["-".to_owned()], }; //println!("{:#?}", options); diff --git a/src/uu/install/src/install.rs b/src/uu/install/src/install.rs index 63ba52b1c8d..43925a7f8e4 100644 --- a/src/uu/install/src/install.rs +++ b/src/uu/install/src/install.rs @@ -375,9 +375,7 @@ fn behavior(matches: &ArgMatches) -> UResult { }; let backup_mode = backup_control::determine_backup_mode(matches)?; - let target_dir = matches - .get_one::(OPT_TARGET_DIRECTORY) - .map(|d| d.to_owned()); + let target_dir = matches.get_one::(OPT_TARGET_DIRECTORY).cloned(); let preserve_timestamps = matches.get_flag(OPT_PRESERVE_TIMESTAMPS); let compare = matches.get_flag(OPT_COMPARE); @@ -593,7 +591,7 @@ fn standard(mut paths: Vec, b: &Behavior) -> UResult<()> { let source = sources.first().unwrap(); if source.is_dir() { - return Err(InstallError::OmittingDirectory(source.to_path_buf()).into()); + return Err(InstallError::OmittingDirectory(source.clone()).into()); } if target.is_file() || is_new_file_path(&target) { @@ -628,7 +626,7 @@ fn copy_files_into_dir(files: &[PathBuf], target_dir: &Path, b: &Behavior) -> UR } if sourcepath.is_dir() { - let err = InstallError::OmittingDirectory(sourcepath.to_path_buf()); + let err = InstallError::OmittingDirectory(sourcepath.clone()); show!(err); continue; } @@ -701,12 +699,9 @@ fn perform_backup(to: &Path, b: &Behavior) -> UResult> { if let Some(ref backup_path) = backup_path { // TODO!! if let Err(err) = fs::rename(to, backup_path) { - return Err(InstallError::BackupFailed( - to.to_path_buf(), - backup_path.to_path_buf(), - err, - ) - .into()); + return Err( + InstallError::BackupFailed(to.to_path_buf(), backup_path.clone(), err).into(), + ); } } Ok(backup_path) diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 327b914dd9e..88af56bb186 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -577,9 +577,7 @@ fn extract_color(options: &clap::ArgMatches) -> bool { /// /// A QuotingStyle variant representing the quoting style to use. fn extract_quoting_style(options: &clap::ArgMatches, show_control: bool) -> QuotingStyle { - let opt_quoting_style = options - .get_one::(options::QUOTING_STYLE) - .map(|cmd_line_qs| cmd_line_qs.to_owned()); + let opt_quoting_style = options.get_one::(options::QUOTING_STYLE).cloned(); if let Some(style) = opt_quoting_style { match style.as_str() { @@ -788,9 +786,7 @@ impl Config { match parse_size_u64(&raw_bs.to_string_lossy()) { Ok(size) => Some(size), Err(_) => { - show!(LsError::BlockSizeParseError( - cmd_line_bs.unwrap().to_owned() - )); + show!(LsError::BlockSizeParseError(cmd_line_bs.unwrap().clone())); None } } @@ -3056,7 +3052,7 @@ fn display_file_name( target_data.must_dereference, ) { Ok(md) => md, - Err(_) => path.md(out).unwrap().to_owned(), + Err(_) => path.md(out).unwrap().clone(), }; name.push_str(&color_name( @@ -3073,11 +3069,7 @@ fn display_file_name( } } Err(err) => { - show!(LsError::IOErrorContext( - err, - path.p_buf.to_path_buf(), - false - )); + show!(LsError::IOErrorContext(err, path.p_buf.clone(), false)); } } } @@ -3087,7 +3079,7 @@ fn display_file_name( if config.context { if let Some(pad_count) = prefix_context { let security_context = if matches!(config.format, Format::Commas) { - path.security_context.to_owned() + path.security_context.clone() } else { pad_left(&path.security_context, pad_count) }; diff --git a/src/uu/mkfifo/src/mkfifo.rs b/src/uu/mkfifo/src/mkfifo.rs index dc1f876fcf1..39d1127394e 100644 --- a/src/uu/mkfifo/src/mkfifo.rs +++ b/src/uu/mkfifo/src/mkfifo.rs @@ -42,7 +42,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { }; let fifos: Vec = match matches.get_many::(options::FIFO) { - Some(v) => v.clone().map(|s| s.to_owned()).collect(), + Some(v) => v.cloned().collect(), None => return Err(USimpleError::new(1, "missing operand")), }; diff --git a/src/uu/mv/src/mv.rs b/src/uu/mv/src/mv.rs index 0ceda8e75e8..036024f9907 100644 --- a/src/uu/mv/src/mv.rs +++ b/src/uu/mv/src/mv.rs @@ -146,7 +146,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let files: Vec = matches .get_many::(ARG_FILES) .unwrap_or_default() - .map(|v| v.to_os_string()) + .cloned() .collect(); let overwrite_mode = determine_overwrite_mode(&matches); diff --git a/src/uu/nl/src/helper.rs b/src/uu/nl/src/helper.rs index ae14a6d59cc..e617010c147 100644 --- a/src/uu/nl/src/helper.rs +++ b/src/uu/nl/src/helper.rs @@ -19,11 +19,11 @@ pub fn parse_options(settings: &mut crate::Settings, opts: &clap::ArgMatches) -> settings.section_delimiter = if delimiter.len() == 1 { format!("{delimiter}:") } else { - delimiter.to_owned() + delimiter.clone() }; } if let Some(val) = opts.get_one::(options::NUMBER_SEPARATOR) { - settings.number_separator = val.to_owned(); + settings.number_separator = val.clone(); } settings.number_format = opts .get_one::(options::NUMBER_FORMAT) diff --git a/src/uu/nl/src/nl.rs b/src/uu/nl/src/nl.rs index 61ca8406f1e..eaf27f3b6f4 100644 --- a/src/uu/nl/src/nl.rs +++ b/src/uu/nl/src/nl.rs @@ -195,7 +195,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } let files: Vec = match matches.get_many::(options::FILE) { - Some(v) => v.clone().map(|v| v.to_owned()).collect(), + Some(v) => v.cloned().collect(), None => vec!["-".to_owned()], }; diff --git a/src/uu/numfmt/src/numfmt.rs b/src/uu/numfmt/src/numfmt.rs index 4afe5655552..d1785209d06 100644 --- a/src/uu/numfmt/src/numfmt.rs +++ b/src/uu/numfmt/src/numfmt.rs @@ -211,9 +211,7 @@ fn parse_options(args: &ArgMatches) -> Result { _ => unreachable!("Should be restricted by clap"), }; - let suffix = args - .get_one::(options::SUFFIX) - .map(|s| s.to_owned()); + let suffix = args.get_one::(options::SUFFIX).cloned(); let invalid = InvalidModes::from_str(args.get_one::(options::INVALID).unwrap()).unwrap(); diff --git a/src/uu/paste/src/paste.rs b/src/uu/paste/src/paste.rs index 89bba034c3e..118a66b81c2 100644 --- a/src/uu/paste/src/paste.rs +++ b/src/uu/paste/src/paste.rs @@ -44,7 +44,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let files = matches .get_many::(options::FILE) .unwrap() - .map(|s| s.to_owned()) + .cloned() .collect(); let line_ending = LineEnding::from_zero_flag(matches.get_flag(options::ZERO_TERMINATED)); diff --git a/src/uu/ptx/src/ptx.rs b/src/uu/ptx/src/ptx.rs index 1e9532a3a12..6dd2b2992e0 100644 --- a/src/uu/ptx/src/ptx.rs +++ b/src/uu/ptx/src/ptx.rs @@ -720,7 +720,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let matches = uu_app().try_get_matches_from(args)?; let mut input_files: Vec = match &matches.get_many::(options::FILE) { - Some(v) => v.clone().map(|v| v.to_owned()).collect(), + Some(v) => v.clone().cloned().collect(), None => vec!["-".to_string()], }; diff --git a/src/uu/shuf/src/shuf.rs b/src/uu/shuf/src/shuf.rs index 1b21b953274..8c636f1cb1d 100644 --- a/src/uu/shuf/src/shuf.rs +++ b/src/uu/shuf/src/shuf.rs @@ -75,7 +75,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let headcounts = matches .get_many::(options::HEAD_COUNT) .unwrap_or_default() - .map(|s| s.to_owned()) + .cloned() .collect(); match parse_head_count(headcounts) { Ok(val) => val, diff --git a/src/uu/sort/src/numeric_str_cmp.rs b/src/uu/sort/src/numeric_str_cmp.rs index 661f536a38c..c6af856c2eb 100644 --- a/src/uu/sort/src/numeric_str_cmp.rs +++ b/src/uu/sort/src/numeric_str_cmp.rs @@ -394,8 +394,8 @@ mod tests { let (a_info, a_range) = NumInfo::parse(a, &NumInfoParseSettings::default()); let (b_info, b_range) = NumInfo::parse(b, &NumInfoParseSettings::default()); let ordering = numeric_str_cmp( - (&a[a_range.to_owned()], &a_info), - (&b[b_range.to_owned()], &b_info), + (&a[a_range.clone()], &a_info), + (&b[b_range.clone()], &b_info), ); assert_eq!(ordering, expected); let ordering = numeric_str_cmp((&b[b_range], &b_info), (&a[a_range], &a_info)); diff --git a/src/uu/split/src/split.rs b/src/uu/split/src/split.rs index 4b5cd920715..17a783d72f2 100644 --- a/src/uu/split/src/split.rs +++ b/src/uu/split/src/split.rs @@ -506,10 +506,10 @@ impl Settings { }; let result = Self { - prefix: matches.get_one::(ARG_PREFIX).unwrap().to_owned(), + prefix: matches.get_one::(ARG_PREFIX).unwrap().clone(), suffix, - input: matches.get_one::(ARG_INPUT).unwrap().to_owned(), - filter: matches.get_one::(OPT_FILTER).map(|s| s.to_owned()), + input: matches.get_one::(ARG_INPUT).unwrap().clone(), + filter: matches.get_one::(OPT_FILTER).cloned(), strategy, verbose: matches.value_source(OPT_VERBOSE) == Some(ValueSource::CommandLine), separator, diff --git a/src/uu/sum/src/sum.rs b/src/uu/sum/src/sum.rs index 4616274d024..38ad3964ec8 100644 --- a/src/uu/sum/src/sum.rs +++ b/src/uu/sum/src/sum.rs @@ -107,7 +107,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let matches = uu_app().try_get_matches_from(args)?; let files: Vec = match matches.get_many::(options::FILE) { - Some(v) => v.clone().map(|v| v.to_owned()).collect(), + Some(v) => v.cloned().collect(), None => vec!["-".to_owned()], }; diff --git a/src/uu/tail/src/follow/files.rs b/src/uu/tail/src/follow/files.rs index e4f980267da..d1aa0aed6fc 100644 --- a/src/uu/tail/src/follow/files.rs +++ b/src/uu/tail/src/follow/files.rs @@ -40,7 +40,7 @@ impl FileHandling { pub fn insert(&mut self, k: &Path, v: PathData, update_last: bool) { let k = Self::canonicalize_path(k); if update_last { - self.last = Some(k.to_owned()); + self.last = Some(k.clone()); } let _ = self.map.insert(k, v); } diff --git a/src/uu/tail/src/follow/watch.rs b/src/uu/tail/src/follow/watch.rs index fbda27aa03d..1836a797a96 100644 --- a/src/uu/tail/src/follow/watch.rs +++ b/src/uu/tail/src/follow/watch.rs @@ -279,7 +279,7 @@ impl Observer { if !path.is_file() { continue; } - let mut path = path.to_owned(); + let mut path = path.clone(); if path.is_relative() { path = std::env::current_dir()?.join(path); } @@ -345,7 +345,7 @@ impl Observer { show_error!("{}: file truncated", display_name); self.files.update_reader(event_path)?; } - paths.push(event_path.to_owned()); + paths.push(event_path.clone()); } else if !is_tailable && old_md.is_tailable() { if pd.reader.is_some() { self.files.reset_reader(event_path); @@ -359,7 +359,7 @@ impl Observer { } else if is_tailable { show_error!( "{} has appeared; following new file", display_name.quote()); self.files.update_reader(event_path)?; - paths.push(event_path.to_owned()); + paths.push(event_path.clone()); } else if settings.retry { if self.follow_descriptor() { show_error!( @@ -403,7 +403,7 @@ impl Observer { "{} cannot be used, reverting to polling", text::BACKEND ); - self.orphans.push(event_path.to_owned()); + self.orphans.push(event_path.clone()); let _ = self.watcher_rx.as_mut().unwrap().unwatch(event_path); } } else { @@ -451,7 +451,7 @@ impl Observer { if self.follow_descriptor() { let new_path = event.paths.last().unwrap(); - paths.push(new_path.to_owned()); + paths.push(new_path.clone()); let new_data = PathData::from_other_with_path(self.files.remove(event_path), new_path); self.files.insert( diff --git a/src/uu/test/src/parser.rs b/src/uu/test/src/parser.rs index 2b847fa1514..23a2d7cf66e 100644 --- a/src/uu/test/src/parser.rs +++ b/src/uu/test/src/parser.rs @@ -164,7 +164,7 @@ impl Parser { /// The stream is unchanged and will return the same Symbol on subsequent /// calls to `next()` or `peek()`. fn peek(&mut self) -> Symbol { - Symbol::new(self.tokens.peek().map(|s| s.to_os_string())) + Symbol::new(self.tokens.peek().cloned()) } /// Test if the next token in the stream is a BOOLOP (-a or -o), without From 91b19b7c56fa00d66f8cd8bdee1725daced19302 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Sun, 5 Nov 2023 13:56:39 +0100 Subject: [PATCH 0306/2851] cp,tail: fix warnings in tests on Android --- tests/by-util/test_cp.rs | 4 ++-- tests/by-util/test_tail.rs | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/by-util/test_cp.rs b/tests/by-util/test_cp.rs index 8964665b061..311558d387b 100644 --- a/tests/by-util/test_cp.rs +++ b/tests/by-util/test_cp.rs @@ -2120,7 +2120,7 @@ fn test_cp_reflink_insufficient_permission() { .stderr_only("cp: 'unreadable' -> 'existing_file.txt': Permission denied (os error 13)\n"); } -#[cfg(target_os = "linux")] +#[cfg(any(target_os = "linux", target_os = "android"))] #[test] fn test_closes_file_descriptors() { use procfs::process::Process; @@ -3436,7 +3436,7 @@ fn test_cp_debug_sparse_auto() { } #[test] -#[cfg(any(target_os = "linux", target_os = "android", target_os = "macos"))] +#[cfg(any(target_os = "linux", target_os = "macos"))] fn test_cp_debug_reflink_auto() { let ts = TestScenario::new(util_name!()); let at = &ts.fixtures; diff --git a/tests/by-util/test_tail.rs b/tests/by-util/test_tail.rs index bc89f56a03c..c7ca09af8b9 100644 --- a/tests/by-util/test_tail.rs +++ b/tests/by-util/test_tail.rs @@ -23,6 +23,7 @@ use std::io::Write; use std::io::{Seek, SeekFrom}; #[cfg(all( not(target_vendor = "apple"), + not(target_os = "android"), not(target_os = "windows"), not(target_os = "freebsd") ))] @@ -31,6 +32,7 @@ use std::process::Stdio; use tail::chunks::BUFFER_SIZE as CHUNK_BUFFER_SIZE; #[cfg(all( not(target_vendor = "apple"), + not(target_os = "android"), not(target_os = "windows"), not(target_os = "freebsd") ))] From bbdde2890a8b3ca2e9ea55a2a817ec34c1c3bca3 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Sun, 5 Nov 2023 14:19:43 +0100 Subject: [PATCH 0307/2851] du: ignore test under Android & FreeBSD --- tests/by-util/test_du.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/by-util/test_du.rs b/tests/by-util/test_du.rs index 7e6bc47756a..fdb44ef536a 100644 --- a/tests/by-util/test_du.rs +++ b/tests/by-util/test_du.rs @@ -336,7 +336,7 @@ fn _du_dereference(s: &str) { } } -#[cfg(not(windows))] +#[cfg(not(any(target_os = "windows", target_os = "android", target_os = "freebsd")))] #[test] fn test_du_no_dereference() { let ts = TestScenario::new(util_name!()); From 4f14a9d55275a8c327ad23a6a42ded8f8dc76ce3 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 5 Nov 2023 13:50:12 +0000 Subject: [PATCH 0308/2851] chore(deps): update rust crate libc to 0.2.150 --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 048b0026362..3a3e4b2c4d2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1187,9 +1187,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.149" +version = "0.2.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" +checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" [[package]] name = "libloading" diff --git a/Cargo.toml b/Cargo.toml index a565370a4e3..fac6f5e899a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -286,7 +286,7 @@ glob = "0.3.1" half = "2.3" indicatif = "0.17" itertools = "0.11.0" -libc = "0.2.149" +libc = "0.2.150" lscolors = { version = "0.15.0", default-features = false, features = [ "nu-ansi-term", ] } From e11878e7ba9f6344aa59b21722ba0c042176af6b Mon Sep 17 00:00:00 2001 From: Alexandre Hausen Date: Sun, 5 Nov 2023 20:58:04 -0300 Subject: [PATCH 0309/2851] du: remove crash! macro --- src/uu/du/src/du.rs | 49 ++++++++++++++++++++++++++------------------- 1 file changed, 28 insertions(+), 21 deletions(-) diff --git a/src/uu/du/src/du.rs b/src/uu/du/src/du.rs index ae88ed13bd1..ddfbc5cc494 100644 --- a/src/uu/du/src/du.rs +++ b/src/uu/du/src/du.rs @@ -31,13 +31,11 @@ use std::time::{Duration, UNIX_EPOCH}; use std::{error::Error, fmt::Display}; use uucore::display::{print_verbatim, Quotable}; use uucore::error::FromIo; -use uucore::error::{set_exit_code, UError, UResult}; +use uucore::error::{set_exit_code, UError, UResult, USimpleError}; use uucore::line_ending::LineEnding; use uucore::parse_glob; use uucore::parse_size::{parse_size_u64, ParseSizeError}; -use uucore::{ - crash, format_usage, help_about, help_section, help_usage, show, show_error, show_warning, -}; +use uucore::{format_usage, help_about, help_section, help_usage, show, show_error, show_warning}; #[cfg(windows)] use windows_sys::Win32::Foundation::HANDLE; #[cfg(windows)] @@ -255,22 +253,27 @@ fn get_file_info(path: &Path) -> Option { result } -fn read_block_size(s: Option<&str>) -> u64 { +fn read_block_size(s: Option<&str>) -> UResult { if let Some(s) = s { - parse_size_u64(s) - .unwrap_or_else(|e| crash!(1, "{}", format_error_message(&e, s, options::BLOCK_SIZE))) + match parse_size_u64(s) { + Ok(x) => Ok(x), + Err(e) => Err(USimpleError::new( + 1, + format_error_message(&e, s, options::BLOCK_SIZE), + )), + } } else { for env_var in ["DU_BLOCK_SIZE", "BLOCK_SIZE", "BLOCKSIZE"] { if let Ok(env_size) = env::var(env_var) { if let Ok(v) = parse_size_u64(&env_size) { - return v; + return Ok(v); } } } if env::var("POSIXLY_CORRECT").is_ok() { - 512 + Ok(512) } else { - 1024 + Ok(1024) } } } @@ -574,12 +577,20 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { matches .get_one::(options::BLOCK_SIZE) .map(|s| s.as_str()), - ); + )?; - let threshold = matches.get_one::(options::THRESHOLD).map(|s| { - Threshold::from_str(s) - .unwrap_or_else(|e| crash!(1, "{}", format_error_message(&e, s, options::THRESHOLD))) - }); + let threshold = match matches.get_one::(options::THRESHOLD) { + Some(s) => match Threshold::from_str(s) { + Ok(t) => Some(t), + Err(e) => { + return Err(USimpleError::new( + 1, + format_error_message(&e, s, options::THRESHOLD), + )) + } + }, + None => None, + }; let multiplier: u64 = if matches.get_flag(options::SI) { 1000 @@ -991,13 +1002,9 @@ mod test_du { #[test] fn test_read_block_size() { - let test_data = [ - (Some("1024".to_string()), 1024), - (Some("K".to_string()), 1024), - (None, 1024), - ]; + let test_data = [Some("1024".to_string()), Some("K".to_string()), None]; for it in &test_data { - assert_eq!(read_block_size(it.0.as_deref()), it.1); + assert!(matches!(read_block_size(it.as_deref()), Ok(1024))); } } } From 2571af8ededb52c53097508e4f2e848549f9075c Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Mon, 6 Nov 2023 10:15:47 +0100 Subject: [PATCH 0310/2851] du: use blocks to remove some cfgs --- src/uu/du/src/du.rs | 65 ++++++++++++++++++++++++--------------------- 1 file changed, 34 insertions(+), 31 deletions(-) diff --git a/src/uu/du/src/du.rs b/src/uu/du/src/du.rs index 9139c31c313..53577dcd576 100644 --- a/src/uu/du/src/du.rs +++ b/src/uu/du/src/du.rs @@ -137,39 +137,42 @@ impl Stat { }?; #[cfg(not(windows))] - let file_info = FileInfo { - file_id: metadata.ino() as u128, - dev_id: metadata.dev(), - }; - #[cfg(not(windows))] - return Ok(Self { - path: path.to_path_buf(), - is_dir: metadata.is_dir(), - size: if path.is_dir() { 0 } else { metadata.len() }, - blocks: metadata.blocks(), - inodes: 1, - inode: Some(file_info), - created: birth_u64(&metadata), - accessed: metadata.atime() as u64, - modified: metadata.mtime() as u64, - }); + { + let file_info = FileInfo { + file_id: metadata.ino() as u128, + dev_id: metadata.dev(), + }; + + return Ok(Self { + path: path.to_path_buf(), + is_dir: metadata.is_dir(), + size: if path.is_dir() { 0 } else { metadata.len() }, + blocks: metadata.blocks(), + inodes: 1, + inode: Some(file_info), + created: birth_u64(&metadata), + accessed: metadata.atime() as u64, + modified: metadata.mtime() as u64, + }); + } #[cfg(windows)] - let size_on_disk = get_size_on_disk(path); - #[cfg(windows)] - let file_info = get_file_info(path); - #[cfg(windows)] - Ok(Self { - path: path.to_path_buf(), - is_dir: metadata.is_dir(), - size: if path.is_dir() { 0 } else { metadata.len() }, - blocks: size_on_disk / 1024 * 2, - inode: file_info, - inodes: 1, - created: windows_creation_time_to_unix_time(metadata.creation_time()), - accessed: windows_time_to_unix_time(metadata.last_access_time()), - modified: windows_time_to_unix_time(metadata.last_write_time()), - }) + { + let size_on_disk = get_size_on_disk(path); + let file_info = get_file_info(path); + + Ok(Self { + path: path.to_path_buf(), + is_dir: metadata.is_dir(), + size: if path.is_dir() { 0 } else { metadata.len() }, + blocks: size_on_disk / 1024 * 2, + inodes: 1, + inode: file_info, + created: windows_creation_time_to_unix_time(metadata.creation_time()), + accessed: windows_time_to_unix_time(metadata.last_access_time()), + modified: windows_time_to_unix_time(metadata.last_write_time()), + }) + } } } From 993a995f8aa4ad04e46d79f4035dbf3eee02a953 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Mon, 6 Nov 2023 10:21:24 +0100 Subject: [PATCH 0311/2851] du: remove unnecessary return --- src/uu/du/src/du.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/uu/du/src/du.rs b/src/uu/du/src/du.rs index 53577dcd576..0d5e34fdeb8 100644 --- a/src/uu/du/src/du.rs +++ b/src/uu/du/src/du.rs @@ -143,7 +143,7 @@ impl Stat { dev_id: metadata.dev(), }; - return Ok(Self { + Ok(Self { path: path.to_path_buf(), is_dir: metadata.is_dir(), size: if path.is_dir() { 0 } else { metadata.len() }, @@ -153,7 +153,7 @@ impl Stat { created: birth_u64(&metadata), accessed: metadata.atime() as u64, modified: metadata.mtime() as u64, - }); + }) } #[cfg(windows)] From 7afb8461cbcccb54f8285617480c654b1ae160b8 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Tue, 7 Nov 2023 10:30:54 +0100 Subject: [PATCH 0312/2851] du: add -H (alias for --dereference-args) --- src/uu/du/src/du.rs | 1 + tests/by-util/test_du.rs | 10 ++++++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/uu/du/src/du.rs b/src/uu/du/src/du.rs index 9139c31c313..407d90a546c 100644 --- a/src/uu/du/src/du.rs +++ b/src/uu/du/src/du.rs @@ -821,6 +821,7 @@ pub fn uu_app() -> Command { .arg( Arg::new(options::DEREFERENCE_ARGS) .short('D') + .visible_short_alias('H') .long(options::DEREFERENCE_ARGS) .help("follow only symlinks that are listed on the command line") .action(ArgAction::SetTrue) diff --git a/tests/by-util/test_du.rs b/tests/by-util/test_du.rs index fdb44ef536a..37594217d44 100644 --- a/tests/by-util/test_du.rs +++ b/tests/by-util/test_du.rs @@ -299,11 +299,13 @@ fn test_du_dereference_args() { file2.write_all(b"amaz?ng").unwrap(); at.symlink_dir("subdir", "sublink"); - let result = ts.ucmd().arg("-D").arg("-s").arg("sublink").succeeds(); - let stdout = result.stdout_str(); + for arg in ["-D", "-H", "--dereference-args"] { + let result = ts.ucmd().arg(arg).arg("-s").arg("sublink").succeeds(); + let stdout = result.stdout_str(); - assert!(!stdout.starts_with('0')); - assert!(stdout.contains("sublink")); + assert!(!stdout.starts_with('0')); + assert!(stdout.contains("sublink")); + } // Without the option let result = ts.ucmd().arg("-s").arg("sublink").succeeds(); From 1a457c00aa99ecdf67c15673a93b70d56709f3aa Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Tue, 7 Nov 2023 11:37:17 +0100 Subject: [PATCH 0313/2851] CONTRIBUTING.md: start on git etiquette --- CONTRIBUTING.md | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f59d3b4f87f..ee912d786ce 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -183,7 +183,27 @@ outdated comments! ## Git Etiquette -todo +To ensure easy collaboration, we have guidelines for using Git and GitHub. + +### Commits + +- Make small and atomic commits. +- Keep a clean history of commits. +- Write informative commit messages. +- Annotate your commit message with the component you're editing. For example: `cp: do not overwrite on with -i` or `uucore: add support for FreeBSD`. +- Do not unnecessarily move items around in the code. This makes the changes much harder to review. If you do need to move things around, do that in a separate commit. + +### PRs + +- Make the titles of PRs descriptive. + - This means describing the problem you solve. For example, do not write `Fix #1234`, but `ls: fix version sort order`. + - You can prefix the title with the utility the PR concerns. +- Keep PRs small and self-contained. A set of small PRs is much more likely to get merged quickly than one large PR. +- Make sure the CI passes (up to intermittently failing tests). +- You know your code best, that's why it's best if you can solve merge conflicts on your branch yourself. + - It's up to you whether you want to use `git merge main` or `git rebase main`. + - Feel free to ask for help with merge conflicts. +- You do not need to ping maintainers to request a review, but it's fine to do so if you don't get a response within a few days. ## Platforms From 6678c17c52b6be1512d568e5b50c80c19018c346 Mon Sep 17 00:00:00 2001 From: Taylor <6225757+tskinn@users.noreply.github.com> Date: Tue, 7 Nov 2023 03:43:58 -0700 Subject: [PATCH 0314/2851] mktemp: add func to expose functionality (for use in nushell) (#5479) * mktemp: add func to expose functionality * mktemp: cleanup --- src/uu/mktemp/src/mktemp.rs | 58 +++++++++++++++++++++++++------------ 1 file changed, 40 insertions(+), 18 deletions(-) diff --git a/src/uu/mktemp/src/mktemp.rs b/src/uu/mktemp/src/mktemp.rs index 77ef5fcbe22..d52351a8948 100644 --- a/src/uu/mktemp/src/mktemp.rs +++ b/src/uu/mktemp/src/mktemp.rs @@ -115,29 +115,30 @@ impl Display for MkTempError { /// This provides a layer of indirection between the application logic /// and the argument parsing library `clap`, allowing each to vary /// independently. -struct Options { +#[derive(Clone)] +pub struct Options { /// Whether to create a temporary directory instead of a file. - directory: bool, + pub directory: bool, /// Whether to just print the name of a file that would have been created. - dry_run: bool, + pub dry_run: bool, /// Whether to suppress file creation error messages. - quiet: bool, + pub quiet: bool, /// The directory in which to create the temporary file. /// /// If `None`, the file will be created in the current directory. - tmpdir: Option, + pub tmpdir: Option, /// The suffix to append to the temporary file, if any. - suffix: Option, + pub suffix: Option, /// Whether to treat the template argument as a single file path component. - treat_as_template: bool, + pub treat_as_template: bool, /// The template to use for the name of the temporary file. - template: String, + pub template: String, } impl Options { @@ -192,7 +193,7 @@ impl Options { /// `num_rand_chars`. struct Params { /// The directory that will contain the temporary file. - directory: String, + directory: PathBuf, /// The (non-random) prefix of the temporary file. prefix: String, @@ -297,7 +298,7 @@ impl Params { let num_rand_chars = j - i; Ok(Self { - directory, + directory: directory.into(), prefix, num_rand_chars, suffix, @@ -357,12 +358,13 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { exec(&tmpdir, &prefix, rand, &suffix, make_dir) }; - if suppress_file_err { + let res = if suppress_file_err { // Mapping all UErrors to ExitCodes prevents the errors from being printed res.map_err(|e| e.code().into()) } else { res - } + }; + println_verbatim(res?).map_err_context(|| "failed to print directory name".to_owned()) } pub fn uu_app() -> Command { @@ -441,7 +443,7 @@ pub fn uu_app() -> Command { .arg(Arg::new(ARG_TEMPLATE).num_args(..=1)) } -pub fn dry_exec(tmpdir: &str, prefix: &str, rand: usize, suffix: &str) -> UResult<()> { +fn dry_exec(tmpdir: &Path, prefix: &str, rand: usize, suffix: &str) -> UResult { let len = prefix.len() + suffix.len() + rand; let mut buf = Vec::with_capacity(len); buf.extend(prefix.as_bytes()); @@ -462,7 +464,7 @@ pub fn dry_exec(tmpdir: &str, prefix: &str, rand: usize, suffix: &str) -> UResul // We guarantee utf8. let buf = String::from_utf8(buf).unwrap(); let tmpdir = Path::new(tmpdir).join(buf); - println_verbatim(tmpdir).map_err_context(|| "failed to print directory name".to_owned()) + Ok(tmpdir) } /// Create a temporary directory with the given parameters. @@ -476,7 +478,7 @@ pub fn dry_exec(tmpdir: &str, prefix: &str, rand: usize, suffix: &str) -> UResul /// /// If the temporary directory could not be written to disk or if the /// given directory `dir` does not exist. -fn make_temp_dir(dir: &str, prefix: &str, rand: usize, suffix: &str) -> UResult { +fn make_temp_dir(dir: &Path, prefix: &str, rand: usize, suffix: &str) -> UResult { let mut builder = Builder::new(); builder.prefix(prefix).rand_bytes(rand).suffix(suffix); match builder.tempdir_in(dir) { @@ -508,7 +510,7 @@ fn make_temp_dir(dir: &str, prefix: &str, rand: usize, suffix: &str) -> UResult< /// /// If the file could not be written to disk or if the directory does /// not exist. -fn make_temp_file(dir: &str, prefix: &str, rand: usize, suffix: &str) -> UResult { +fn make_temp_file(dir: &Path, prefix: &str, rand: usize, suffix: &str) -> UResult { let mut builder = Builder::new(); builder.prefix(prefix).rand_bytes(rand).suffix(suffix); match builder.tempfile_in(dir) { @@ -527,7 +529,7 @@ fn make_temp_file(dir: &str, prefix: &str, rand: usize, suffix: &str) -> UResult } } -fn exec(dir: &str, prefix: &str, rand: usize, suffix: &str, make_dir: bool) -> UResult<()> { +fn exec(dir: &Path, prefix: &str, rand: usize, suffix: &str, make_dir: bool) -> UResult { let path = if make_dir { make_temp_dir(dir, prefix, rand, suffix)? } else { @@ -546,7 +548,27 @@ fn exec(dir: &str, prefix: &str, rand: usize, suffix: &str, make_dir: bool) -> U // relative path. let path = Path::new(dir).join(filename); - println_verbatim(path).map_err_context(|| "failed to print directory name".to_owned()) + Ok(path) +} + +/// Create a temporary file or directory +/// +/// Behavior is determined by the `options` parameter, see [`Options`] for details. +pub fn mktemp(options: &Options) -> UResult { + // Parse file path parameters from the command-line options. + let Params { + directory: tmpdir, + prefix, + num_rand_chars: rand, + suffix, + } = Params::from(options.clone())?; + + // Create the temporary file or directory, or simulate creating it. + if options.dry_run { + dry_exec(&tmpdir, &prefix, rand, &suffix) + } else { + exec(&tmpdir, &prefix, rand, &suffix, options.directory) + } } #[cfg(test)] From f5709ded4743f96a852baa8134e09637b9619987 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Tue, 7 Nov 2023 12:08:44 +0100 Subject: [PATCH 0315/2851] CONTRIBUTING.md: move and shorten commit message advice --- CONTRIBUTING.md | 135 ++++++++++++++++++++---------------------------- 1 file changed, 56 insertions(+), 79 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ee912d786ce..aea215aece0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -14,8 +14,7 @@ check out these documents: Now follows a very important warning: -> [!WARNING] -> uutils is original code and cannot contain any code from GNU or +> [!WARNING] uutils is original code and cannot contain any code from GNU or > other implementations. This means that **we cannot accept any changes based on > the GNU source code**. To make sure that cannot happen, **you cannot link to > the GNU source code** either. @@ -53,15 +52,19 @@ use by others: We have the following goals with our development: -- **Compatible**: The utilities should be a drop-in replacement for the GNU coreutils. -- **Cross-platform**: All utilities should run on as many of the supported platforms as possible. +- **Compatible**: The utilities should be a drop-in replacement for the GNU + coreutils. +- **Cross-platform**: All utilities should run on as many of the supported + platforms as possible. - **Reliable**: The utilities should never unexpectedly fail. -- **Performant**: Our utilities should be written in fast idiomatic Rust. We aim to match or exceed the performance of the GNU utilities. -- **Well-tested**: We should have a lot of tests to be able to guarantee reliability and compatibility. +- **Performant**: Our utilities should be written in fast idiomatic Rust. We aim + to match or exceed the performance of the GNU utilities. +- **Well-tested**: We should have a lot of tests to be able to guarantee + reliability and compatibility. ## How to Help -There are several ways to help and writing code is just one them. Reporting +There are several ways to help and writing code is just one of them. Reporting issues and writing documentation are just as important as writing code. ### Reporting Issues @@ -92,7 +95,7 @@ work into a fix that we can't merge! If there's no issue for what you're trying to fix yet, make one _before_ you start working on the PR. Generally, we try to follow what GNU is doing in terms of options and behavior. -It is recommended to look at the GNU Coreutils manual +It is recommended to look at the GNU coreutils manual ([on the web](https://www.gnu.org/software/coreutils/manual/html_node/index.html), or locally using `info `). It is more in depth than the man pages and provides a good description of available features and their implementation @@ -190,20 +193,58 @@ To ensure easy collaboration, we have guidelines for using Git and GitHub. - Make small and atomic commits. - Keep a clean history of commits. - Write informative commit messages. -- Annotate your commit message with the component you're editing. For example: `cp: do not overwrite on with -i` or `uucore: add support for FreeBSD`. -- Do not unnecessarily move items around in the code. This makes the changes much harder to review. If you do need to move things around, do that in a separate commit. +- Annotate your commit message with the component you're editing. For example: + `cp: do not overwrite on with -i` or `uucore: add support for FreeBSD`. +- Do not unnecessarily move items around in the code. This makes the changes + much harder to review. If you do need to move things around, do that in a + separate commit. + +### Commit messages + +You can read this section in the Git book to learn how to write good commit +messages: https://git-scm.com/book/ch5-2.html. + +In addition, here are a few examples for a summary line when committing to +uutils: + +- commit for a single utility + +``` +nohup: cleanup and refactor +``` + +- commit for a utility's tests + +``` +tests/rm: test new feature +``` + +Beyond changes to an individual utility or its tests, other summary lines for +non-utility modules include: + +``` +README: add help +uucore: add new modules +uutils: add new utility +gitignore: add temporary files +``` ### PRs - Make the titles of PRs descriptive. - - This means describing the problem you solve. For example, do not write `Fix #1234`, but `ls: fix version sort order`. + - This means describing the problem you solve. For example, do not write + `Fix #1234`, but `ls: fix version sort order`. - You can prefix the title with the utility the PR concerns. -- Keep PRs small and self-contained. A set of small PRs is much more likely to get merged quickly than one large PR. +- Keep PRs small and self-contained. A set of small PRs is much more likely to + get merged quickly than one large PR. - Make sure the CI passes (up to intermittently failing tests). -- You know your code best, that's why it's best if you can solve merge conflicts on your branch yourself. - - It's up to you whether you want to use `git merge main` or `git rebase main`. +- You know your code best, that's why it's best if you can solve merge conflicts + on your branch yourself. + - It's up to you whether you want to use `git merge main` or + `git rebase main`. - Feel free to ask for help with merge conflicts. -- You do not need to ping maintainers to request a review, but it's fine to do so if you don't get a response within a few days. +- You do not need to ping maintainers to request a review, but it's fine to do + so if you don't get a response within a few days. ## Platforms @@ -246,70 +287,6 @@ To improve the GNU compatibility, the following process is recommended: 1. Start to modify the Rust implementation to match the expected behavior 1. Add a test to make sure that we don't regress (our test suite is super quick) -## Commit messages - -To help the project maintainers review pull requests from contributors across -numerous utilities, the team has settled on conventions for commit messages. - -From : - -``` -Capitalized, short (50 chars or less) summary - -More detailed explanatory text, if necessary. Wrap it to about 72 -characters or so. In some contexts, the first line is treated as the -subject of an email and the rest of the text as the body. The blank -line separating the summary from the body is critical (unless you omit -the body entirely); tools like rebase will confuse you if you run the -two together. - -Write your commit message in the imperative: "Fix bug" and not "Fixed bug" -or "Fixes bug." This convention matches up with commit messages generated -by commands like git merge and git revert. - -Further paragraphs come after blank lines. - - - Bullet points are okay, too - - - Typically a hyphen or asterisk is used for the bullet, followed by a - single space, with blank lines in between, but conventions vary here - - - Use a hanging indent -``` - -Furthermore, here are a few examples for a summary line: - -- commit for a single utility - -``` -nohup: cleanup and refactor -``` - -- commit for a utility's tests - -``` -tests/rm: test new feature -``` - -Beyond changes to an individual utility or its tests, other summary lines for -non-utility modules include: - -``` -README: add help -``` - -``` -uucore: add new modules -``` - -``` -uutils: add new utility -``` - -``` -gitignore: add temporary files -``` - ## Code coverage To generate code coverage report locally please follow From 46d4ebff4c32f135406aff9489c6859f9bec549c Mon Sep 17 00:00:00 2001 From: clint Date: Tue, 7 Nov 2023 18:02:13 -0500 Subject: [PATCH 0316/2851] expand: remove crash! macro --- src/uu/expand/src/expand.rs | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/uu/expand/src/expand.rs b/src/uu/expand/src/expand.rs index 9294d1a8fa1..71d03a49afc 100644 --- a/src/uu/expand/src/expand.rs +++ b/src/uu/expand/src/expand.rs @@ -15,7 +15,7 @@ use std::str::from_utf8; use unicode_width::UnicodeWidthChar; use uucore::display::Quotable; use uucore::error::{FromIo, UError, UResult}; -use uucore::{crash, format_usage, help_about, help_usage}; +use uucore::{format_usage, help_about, help_usage}; const ABOUT: &str = help_about!("expand.md"); const USAGE: &str = help_usage!("expand.md"); @@ -308,16 +308,13 @@ pub fn uu_app() -> Command { ) } -fn open(path: &str) -> BufReader> { +fn open(path: &str) -> std::io::Result>> { let file_buf; if path == "-" { - BufReader::new(Box::new(stdin()) as Box) + Ok(BufReader::new(Box::new(stdin()) as Box)) } else { - file_buf = match File::open(path) { - Ok(a) => a, - Err(e) => crash!(1, "{}: {}\n", path.maybe_quote(), e), - }; - BufReader::new(Box::new(file_buf) as Box) + file_buf = File::open(path)?; + Ok(BufReader::new(Box::new(file_buf) as Box)) } } @@ -378,7 +375,7 @@ fn expand(options: &Options) -> std::io::Result<()> { let mut buf = Vec::new(); for file in &options.files { - let mut fh = open(file); + let mut fh = open(file)?; while match fh.read_until(b'\n', &mut buf) { Ok(s) => s > 0, From 3411c25112f31acb5b442c6bd39901b41104b7d6 Mon Sep 17 00:00:00 2001 From: clint Date: Tue, 7 Nov 2023 20:18:58 -0500 Subject: [PATCH 0317/2851] expand: make error output the same as it was --- src/uu/expand/src/expand.rs | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/src/uu/expand/src/expand.rs b/src/uu/expand/src/expand.rs index 71d03a49afc..5791fca9fd2 100644 --- a/src/uu/expand/src/expand.rs +++ b/src/uu/expand/src/expand.rs @@ -265,7 +265,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let matches = uu_app().try_get_matches_from(expand_shortcuts(&args))?; - expand(&Options::new(&matches)?).map_err_context(|| "failed to write output".to_string()) + expand(&Options::new(&matches)?) } pub fn uu_app() -> Command { @@ -308,12 +308,12 @@ pub fn uu_app() -> Command { ) } -fn open(path: &str) -> std::io::Result>> { +fn open(path: &str) -> UResult>> { let file_buf; if path == "-" { Ok(BufReader::new(Box::new(stdin()) as Box)) } else { - file_buf = File::open(path)?; + file_buf = File::open(path).map_err_context(|| path.to_string())?; Ok(BufReader::new(Box::new(file_buf) as Box)) } } @@ -367,7 +367,7 @@ enum CharType { } #[allow(clippy::cognitive_complexity)] -fn expand(options: &Options) -> std::io::Result<()> { +fn expand(options: &Options) -> UResult<()> { use self::CharType::*; let mut output = BufWriter::new(stdout()); @@ -428,12 +428,18 @@ fn expand(options: &Options) -> std::io::Result<()> { // now dump out either spaces if we're expanding, or a literal tab if we're not if init || !options.iflag { if nts <= options.tspaces.len() { - output.write_all(options.tspaces[..nts].as_bytes())?; + output + .write_all(options.tspaces[..nts].as_bytes()) + .map_err_context(|| "failed to write output".to_string())?; } else { - output.write_all(" ".repeat(nts).as_bytes())?; + output + .write_all(" ".repeat(nts).as_bytes()) + .map_err_context(|| "failed to write output".to_string())?; }; } else { - output.write_all(&buf[byte..byte + nbytes])?; + output + .write_all(&buf[byte..byte + nbytes]) + .map_err_context(|| "failed to write output".to_string())?; } } _ => { @@ -451,14 +457,18 @@ fn expand(options: &Options) -> std::io::Result<()> { init = false; } - output.write_all(&buf[byte..byte + nbytes])?; + output + .write_all(&buf[byte..byte + nbytes]) + .map_err_context(|| "failed to write output".to_string())?; } } byte += nbytes; // advance the pointer } - output.flush()?; + output + .flush() + .map_err_context(|| "failed to write output".to_string())?; buf.truncate(0); // clear the buffer } } From 761213f1d2ad1784a00e9145817bd7c4919d92dc Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Tue, 7 Nov 2023 15:47:04 +0100 Subject: [PATCH 0318/2851] cp: make test_closes_file_descriptors Linux-only --- tests/by-util/test_cp.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/by-util/test_cp.rs b/tests/by-util/test_cp.rs index 311558d387b..c8761fab8fb 100644 --- a/tests/by-util/test_cp.rs +++ b/tests/by-util/test_cp.rs @@ -24,8 +24,6 @@ use std::path::PathBuf; #[cfg(any(target_os = "linux", target_os = "android"))] use filetime::FileTime; -#[cfg(any(target_os = "linux", target_os = "android"))] -use rlimit::Resource; #[cfg(target_os = "linux")] use std::ffi::OsString; #[cfg(any(target_os = "linux", target_os = "android"))] @@ -2120,10 +2118,11 @@ fn test_cp_reflink_insufficient_permission() { .stderr_only("cp: 'unreadable' -> 'existing_file.txt': Permission denied (os error 13)\n"); } -#[cfg(any(target_os = "linux", target_os = "android"))] +#[cfg(target_os = "linux")] #[test] fn test_closes_file_descriptors() { use procfs::process::Process; + use rlimit::Resource; let me = Process::myself().unwrap(); // The test suite runs in parallel, we have pipe, sockets @@ -2133,7 +2132,6 @@ fn test_closes_file_descriptors() { let limit_fd: u64 = number_file_already_opened + 9; // For debugging purposes: - #[cfg(not(target_os = "android"))] for f in me.fd().unwrap() { let fd = f.unwrap(); println!("{:?} {:?}", fd, fd.mode()); From a26e3db56e4778a4dd6058b14fad6912324a302b Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Wed, 8 Nov 2023 10:00:26 +0100 Subject: [PATCH 0319/2851] CONTRIBUTING.md: spell-checker:ignore "rustdoc's" --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index aea215aece0..255ed2c53e3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,4 +1,4 @@ - + # Contributing to coreutils From 4d40555cd58dbd82f908cbd20ea57d617fe6e7b3 Mon Sep 17 00:00:00 2001 From: Alexandre Hausen Date: Wed, 8 Nov 2023 20:32:03 -0300 Subject: [PATCH 0320/2851] fixup! du: remove crash! macro --- src/uu/du/src/du.rs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/uu/du/src/du.rs b/src/uu/du/src/du.rs index ddfbc5cc494..3a8ac4b1458 100644 --- a/src/uu/du/src/du.rs +++ b/src/uu/du/src/du.rs @@ -255,13 +255,8 @@ fn get_file_info(path: &Path) -> Option { fn read_block_size(s: Option<&str>) -> UResult { if let Some(s) = s { - match parse_size_u64(s) { - Ok(x) => Ok(x), - Err(e) => Err(USimpleError::new( - 1, - format_error_message(&e, s, options::BLOCK_SIZE), - )), - } + parse_size_u64(s) + .map_err(|e| USimpleError::new(1, format_error_message(&e, s, options::BLOCK_SIZE))) } else { for env_var in ["DU_BLOCK_SIZE", "BLOCK_SIZE", "BLOCKSIZE"] { if let Ok(env_size) = env::var(env_var) { From 7279bc1741c0ee7678a4a66d26392a1ec7dcfed9 Mon Sep 17 00:00:00 2001 From: Zhuoxun Yang Date: Thu, 9 Nov 2023 10:16:53 +0800 Subject: [PATCH 0321/2851] printf.md: support %q --- src/uu/printf/printf.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/uu/printf/printf.md b/src/uu/printf/printf.md index 60b50354c6f..9ce2957701e 100644 --- a/src/uu/printf/printf.md +++ b/src/uu/printf/printf.md @@ -78,6 +78,9 @@ Fields second parameter is min-width, integer output below that width is padded with leading zeroes +* `%q`: ARGUMENT is printed in a format that can be reused as shell input, escaping non-printable + characters with the proposed POSIX $'' syntax. + * `%f` or `%F`: decimal floating point value * `%e` or `%E`: scientific notation floating point value * `%g` or `%G`: shorter of specially interpreted decimal or SciNote floating point value. @@ -181,6 +184,11 @@ All string fields have a 'max width' parameter still be interpreted and not throw a warning, you will have problems if you use this for a literal whose code begins with zero, as it will be viewed as in `\\0NNN` form.) +* `%q`: escaped string - the string in a format that can be reused as input by most shells. + Non-printable characters are escaped with the POSIX proposed ‘$''’ syntax, + and shell meta-characters are quoted appropriately. + This is an equivalent format to ls --quoting=shell-escape output. + #### CHAR SUBSTITUTIONS The character field does not have a secondary parameter. From e3ec12233b04f2846769a585aa0f73b64af08833 Mon Sep 17 00:00:00 2001 From: Zhuoxun Yang Date: Thu, 9 Nov 2023 10:17:44 +0800 Subject: [PATCH 0322/2851] printf: support %q --- src/uucore/src/lib/features/tokenize/sub.rs | 22 ++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/src/uucore/src/lib/features/tokenize/sub.rs b/src/uucore/src/lib/features/tokenize/sub.rs index c65a37a689b..0ae966fc332 100644 --- a/src/uucore/src/lib/features/tokenize/sub.rs +++ b/src/uucore/src/lib/features/tokenize/sub.rs @@ -10,6 +10,7 @@ //! Subs which have numeric field chars make use of the num_format //! submodule use crate::error::{UError, UResult}; +use crate::quoting_style::{escape_name, QuotingStyle}; use itertools::{put_back_n, PutBackN}; use std::error::Error; use std::fmt::Display; @@ -91,7 +92,7 @@ impl Sub { // for more dry printing, field characters are grouped // in initialization of token. let field_type = match field_char { - 's' | 'b' => FieldType::Strf, + 's' | 'b' | 'q' => FieldType::Strf, 'd' | 'i' | 'u' | 'o' | 'x' | 'X' => FieldType::Intf, 'f' | 'F' => FieldType::Floatf, 'a' | 'A' => FieldType::CninetyNineHexFloatf, @@ -189,7 +190,7 @@ impl SubParser { let mut legal_fields = [ // 'a', 'A', //c99 hex float implementation not yet complete - 'b', 'c', 'd', 'e', 'E', 'f', 'F', 'g', 'G', 'i', 'o', 's', 'u', 'x', 'X', + 'b', 'c', 'd', 'e', 'E', 'f', 'F', 'g', 'G', 'i', 'o', 'q', 's', 'u', 'x', 'X', ]; let mut specifiers = ['h', 'j', 'l', 'L', 't', 'z']; legal_fields.sort_unstable(); @@ -260,7 +261,6 @@ impl SubParser { } x if legal_fields.binary_search(&x).is_ok() => { self.field_char = Some(ch); - self.text_so_far.push(ch); break; } x if specifiers.binary_search(&x).is_ok() => { @@ -331,7 +331,7 @@ impl SubParser { if (field_char == 's' && self.min_width_tmp == Some(String::from("0"))) || (field_char == 'c' && (self.min_width_tmp == Some(String::from("0")) || self.past_decimal)) - || (field_char == 'b' + || ((field_char == 'b' || field_char == 'q') && (self.min_width_tmp.is_some() || self.past_decimal || self.second_field_tmp.is_some())) @@ -391,6 +391,7 @@ impl Sub { // if %s just return arg // if %b use UnescapedText module's unescape-fn // if %c return first char of arg + // if %q return arg which non-printable characters are escaped FieldType::Strf | FieldType::Charf => { match pf_arg { Some(arg_string) => { @@ -404,11 +405,18 @@ impl Sub { UnescapedText::from_it_core(writer, &mut a_it, true); None } - // for 'c': get iter of string vals, + 'q' => Some(escape_name( + arg_string.as_ref(), + &QuotingStyle::Shell { + escape: true, + always_quote: false, + show_control: false, + }, + )), // get opt of first val // and map it to opt - /* 'c' | */ - _ => arg_string.chars().next().map(|x| x.to_string()), + 'c' => arg_string.chars().next().map(|x| x.to_string()), + _ => unreachable!(), } } None => None, From fb414ed9179a985de0e545182395a3bd6effbb02 Mon Sep 17 00:00:00 2001 From: Zhuoxun Yang Date: Thu, 9 Nov 2023 10:18:27 +0800 Subject: [PATCH 0323/2851] tests/printf: support %q --- tests/by-util/test_printf.rs | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/tests/by-util/test_printf.rs b/tests/by-util/test_printf.rs index d7ba5679ecf..a297dbf6833 100644 --- a/tests/by-util/test_printf.rs +++ b/tests/by-util/test_printf.rs @@ -112,6 +112,15 @@ fn sub_b_string_handle_escapes() { .stdout_only("hello \tworld"); } +#[test] +fn sub_b_string_validate_field_params() { + new_ucmd!() + .args(&["hello %7b", "world"]) + .run() + .stdout_is("hello ") + .stderr_is("printf: %7b: invalid conversion specification\n"); +} + #[test] fn sub_b_string_ignore_subs() { new_ucmd!() @@ -120,6 +129,31 @@ fn sub_b_string_ignore_subs() { .stdout_only("hello world %% %i"); } +#[test] +fn sub_q_string_non_printable() { + new_ucmd!() + .args(&["non-printable: %q", "\"$test\""]) + .succeeds() + .stdout_only("non-printable: '\"$test\"'"); +} + +#[test] +fn sub_q_string_validate_field_params() { + new_ucmd!() + .args(&["hello %7q", "world"]) + .run() + .stdout_is("hello ") + .stderr_is("printf: %7q: invalid conversion specification\n"); +} + +#[test] +fn sub_q_string_special_non_printable() { + new_ucmd!() + .args(&["non-printable: %q", "test~"]) + .succeeds() + .stdout_only("non-printable: test~"); +} + #[test] fn sub_char() { new_ucmd!() From fc00b6bfc9c5649b428f2420a2d0e23209ee1ae1 Mon Sep 17 00:00:00 2001 From: clint Date: Wed, 8 Nov 2023 23:36:46 -0500 Subject: [PATCH 0324/2851] expand: move logic to expand a single line into its own function --- src/uu/expand/src/expand.rs | 181 ++++++++++++++++++------------------ 1 file changed, 91 insertions(+), 90 deletions(-) diff --git a/src/uu/expand/src/expand.rs b/src/uu/expand/src/expand.rs index 5791fca9fd2..4815c5f683c 100644 --- a/src/uu/expand/src/expand.rs +++ b/src/uu/expand/src/expand.rs @@ -366,10 +366,98 @@ enum CharType { Other, } -#[allow(clippy::cognitive_complexity)] -fn expand(options: &Options) -> UResult<()> { +fn expand_line( + buf: &mut Vec, + output: &mut BufWriter, + ts: &[usize], + options: &Options, +) -> std::io::Result<()> { use self::CharType::*; + let mut col = 0; + let mut byte = 0; + let mut init = true; + + while byte < buf.len() { + let (ctype, cwidth, nbytes) = if options.uflag { + let nbytes = char::from(buf[byte]).len_utf8(); + + if byte + nbytes > buf.len() { + // don't overrun buffer because of invalid UTF-8 + (Other, 1, 1) + } else if let Ok(t) = from_utf8(&buf[byte..byte + nbytes]) { + match t.chars().next() { + Some('\t') => (Tab, 0, nbytes), + Some('\x08') => (Backspace, 0, nbytes), + Some(c) => (Other, UnicodeWidthChar::width(c).unwrap_or(0), nbytes), + None => { + // no valid char at start of t, so take 1 byte + (Other, 1, 1) + } + } + } else { + (Other, 1, 1) // implicit assumption: non-UTF-8 char is 1 col wide + } + } else { + ( + match buf[byte] { + // always take exactly 1 byte in strict ASCII mode + 0x09 => Tab, + 0x08 => Backspace, + _ => Other, + }, + 1, + 1, + ) + }; + + // figure out how many columns this char takes up + match ctype { + Tab => { + // figure out how many spaces to the next tabstop + let nts = next_tabstop(ts, col, &options.remaining_mode); + col += nts; + + // now dump out either spaces if we're expanding, or a literal tab if we're not + if init || !options.iflag { + if nts <= options.tspaces.len() { + output.write_all(options.tspaces[..nts].as_bytes())?; + } else { + output.write_all(" ".repeat(nts).as_bytes())?; + }; + } else { + output.write_all(&buf[byte..byte + nbytes])?; + } + } + _ => { + col = if ctype == Other { + col + cwidth + } else if col > 0 { + col - 1 + } else { + 0 + }; + + // if we're writing anything other than a space, then we're + // done with the line's leading spaces + if buf[byte] != 0x20 { + init = false; + } + + output.write_all(&buf[byte..byte + nbytes])?; + } + } + + byte += nbytes; // advance the pointer + } + + output.flush()?; + buf.truncate(0); // clear the buffer + + Ok(()) +} + +fn expand(options: &Options) -> UResult<()> { let mut output = BufWriter::new(stdout()); let ts = options.tabstops.as_ref(); let mut buf = Vec::new(); @@ -381,95 +469,8 @@ fn expand(options: &Options) -> UResult<()> { Ok(s) => s > 0, Err(_) => buf.is_empty(), } { - let mut col = 0; - let mut byte = 0; - let mut init = true; - - while byte < buf.len() { - let (ctype, cwidth, nbytes) = if options.uflag { - let nbytes = char::from(buf[byte]).len_utf8(); - - if byte + nbytes > buf.len() { - // don't overrun buffer because of invalid UTF-8 - (Other, 1, 1) - } else if let Ok(t) = from_utf8(&buf[byte..byte + nbytes]) { - match t.chars().next() { - Some('\t') => (Tab, 0, nbytes), - Some('\x08') => (Backspace, 0, nbytes), - Some(c) => (Other, UnicodeWidthChar::width(c).unwrap_or(0), nbytes), - None => { - // no valid char at start of t, so take 1 byte - (Other, 1, 1) - } - } - } else { - (Other, 1, 1) // implicit assumption: non-UTF-8 char is 1 col wide - } - } else { - ( - match buf[byte] { - // always take exactly 1 byte in strict ASCII mode - 0x09 => Tab, - 0x08 => Backspace, - _ => Other, - }, - 1, - 1, - ) - }; - - // figure out how many columns this char takes up - match ctype { - Tab => { - // figure out how many spaces to the next tabstop - let nts = next_tabstop(ts, col, &options.remaining_mode); - col += nts; - - // now dump out either spaces if we're expanding, or a literal tab if we're not - if init || !options.iflag { - if nts <= options.tspaces.len() { - output - .write_all(options.tspaces[..nts].as_bytes()) - .map_err_context(|| "failed to write output".to_string())?; - } else { - output - .write_all(" ".repeat(nts).as_bytes()) - .map_err_context(|| "failed to write output".to_string())?; - }; - } else { - output - .write_all(&buf[byte..byte + nbytes]) - .map_err_context(|| "failed to write output".to_string())?; - } - } - _ => { - col = if ctype == Other { - col + cwidth - } else if col > 0 { - col - 1 - } else { - 0 - }; - - // if we're writing anything other than a space, then we're - // done with the line's leading spaces - if buf[byte] != 0x20 { - init = false; - } - - output - .write_all(&buf[byte..byte + nbytes]) - .map_err_context(|| "failed to write output".to_string())?; - } - } - - byte += nbytes; // advance the pointer - } - - output - .flush() + expand_line(&mut buf, &mut output, ts, options) .map_err_context(|| "failed to write output".to_string())?; - buf.truncate(0); // clear the buffer } } Ok(()) From 2e0e88c3acf6b86965bb7ed60a23662d439d74f7 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Wed, 8 Nov 2023 12:46:24 +0100 Subject: [PATCH 0325/2851] fuzz: verify the various steps when opening/closing fd --- fuzz/fuzz_targets/fuzz_common.rs | 49 ++++++++++++++++++++++++++------ 1 file changed, 40 insertions(+), 9 deletions(-) diff --git a/fuzz/fuzz_targets/fuzz_common.rs b/fuzz/fuzz_targets/fuzz_common.rs index a94963ef024..86b8e561827 100644 --- a/fuzz/fuzz_targets/fuzz_common.rs +++ b/fuzz/fuzz_targets/fuzz_common.rs @@ -3,9 +3,10 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -use libc::{dup, dup2, STDOUT_FILENO}; +use libc::{close, dup, dup2, pipe, STDOUT_FILENO}; use std::ffi::OsString; use std::io; +use std::io::Write; use std::process::Command; use std::sync::atomic::Ordering; use std::sync::{atomic::AtomicBool, Once}; @@ -38,18 +39,43 @@ where { let uumain_exit_status; + // Duplicate the stdout file descriptor let original_stdout_fd = unsafe { dup(STDOUT_FILENO) }; - println!("Running test {:?}", &args[1..]); + if original_stdout_fd == -1 { + return ("Failed to duplicate STDOUT_FILENO".to_string(), -1); + } + println!("Running test {:?}", &args[0..]); let mut pipe_fds = [-1; 2]; - unsafe { libc::pipe(pipe_fds.as_mut_ptr()) }; - { - unsafe { dup2(pipe_fds[1], STDOUT_FILENO) }; - uumain_exit_status = uumain_function(args.to_owned().into_iter()); - unsafe { dup2(original_stdout_fd, STDOUT_FILENO) }; - unsafe { libc::close(original_stdout_fd) }; + if unsafe { pipe(pipe_fds.as_mut_ptr()) } == -1 { + return ("Failed to create a pipe".to_string(), -1); + } + + // Redirect stdout to the pipe + unsafe { + if dup2(pipe_fds[1], STDOUT_FILENO) == -1 { + close(pipe_fds[0]); + close(pipe_fds[1]); + return ( + "Failed to redirect STDOUT_FILENO to the pipe".to_string(), + -1, + ); + } } - unsafe { libc::close(pipe_fds[1]) }; + + uumain_exit_status = uumain_function(args.to_owned().into_iter()); + + // Restore the original stdout + unsafe { + if dup2(original_stdout_fd, STDOUT_FILENO) == -1 { + return ( + "Failed to restore the original STDOUT_FILENO".to_string(), + -1, + ); + } + close(original_stdout_fd); + } + unsafe { close(pipe_fds[1]) }; let mut captured_output = Vec::new(); let mut read_buffer = [0; 1024]; @@ -61,6 +87,11 @@ where read_buffer.len(), ) }; + + if bytes_read == -1 { + eprintln!("Failed to read from the pipe"); + break; + } if bytes_read <= 0 { break; } From 5fed5443e43a167d11a334163009e6538ec766f8 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Wed, 8 Nov 2023 12:46:46 +0100 Subject: [PATCH 0326/2851] fuzz: flush after calling uumain - was failing with printf --- fuzz/fuzz_targets/fuzz_common.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/fuzz/fuzz_targets/fuzz_common.rs b/fuzz/fuzz_targets/fuzz_common.rs index 86b8e561827..e30e24ddd3e 100644 --- a/fuzz/fuzz_targets/fuzz_common.rs +++ b/fuzz/fuzz_targets/fuzz_common.rs @@ -64,6 +64,7 @@ where } uumain_exit_status = uumain_function(args.to_owned().into_iter()); + io::stdout().flush().unwrap(); // Restore the original stdout unsafe { From 064ad7200c80a45a3a19e83a8becf73689af153a Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Wed, 8 Nov 2023 23:06:11 +0100 Subject: [PATCH 0327/2851] fuzzing function should also return stderr --- fuzz/fuzz_targets/fuzz_common.rs | 137 ++++++++++++++++++++----------- 1 file changed, 90 insertions(+), 47 deletions(-) diff --git a/fuzz/fuzz_targets/fuzz_common.rs b/fuzz/fuzz_targets/fuzz_common.rs index e30e24ddd3e..9afc2cc8332 100644 --- a/fuzz/fuzz_targets/fuzz_common.rs +++ b/fuzz/fuzz_targets/fuzz_common.rs @@ -3,10 +3,11 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -use libc::{close, dup, dup2, pipe, STDOUT_FILENO}; +use libc::{close, dup, dup2, pipe, STDERR_FILENO, STDOUT_FILENO}; use std::ffi::OsString; use std::io; use std::io::Write; +use std::os::fd::RawFd; use std::process::Command; use std::sync::atomic::Ordering; use std::sync::{atomic::AtomicBool, Once}; @@ -33,57 +34,90 @@ pub fn is_gnu_cmd(cmd_path: &str) -> Result<(), std::io::Error> { } } -pub fn generate_and_run_uumain(args: &[OsString], uumain_function: F) -> (String, i32) +pub fn generate_and_run_uumain(args: &[OsString], uumain_function: F) -> (String, String, i32) where F: FnOnce(std::vec::IntoIter) -> i32, { let uumain_exit_status; - // Duplicate the stdout file descriptor + // Duplicate the stdout and stderr file descriptors let original_stdout_fd = unsafe { dup(STDOUT_FILENO) }; - if original_stdout_fd == -1 { - return ("Failed to duplicate STDOUT_FILENO".to_string(), -1); + let original_stderr_fd = unsafe { dup(STDERR_FILENO) }; + if original_stdout_fd == -1 || original_stderr_fd == -1 { + return ( + "Failed to duplicate STDOUT_FILENO or STDERR_FILENO".to_string(), + "".to_string(), + -1, + ); } println!("Running test {:?}", &args[0..]); - let mut pipe_fds = [-1; 2]; - - if unsafe { pipe(pipe_fds.as_mut_ptr()) } == -1 { - return ("Failed to create a pipe".to_string(), -1); + let mut pipe_stdout_fds = [-1; 2]; + let mut pipe_stderr_fds = [-1; 2]; + + // Create pipes for stdout and stderr + if unsafe { pipe(pipe_stdout_fds.as_mut_ptr()) } == -1 + || unsafe { pipe(pipe_stderr_fds.as_mut_ptr()) } == -1 + { + return ("Failed to create pipes".to_string(), "".to_string(), -1); } - // Redirect stdout to the pipe - unsafe { - if dup2(pipe_fds[1], STDOUT_FILENO) == -1 { - close(pipe_fds[0]); - close(pipe_fds[1]); - return ( - "Failed to redirect STDOUT_FILENO to the pipe".to_string(), - -1, - ); + // Redirect stdout and stderr to their respective pipes + if unsafe { dup2(pipe_stdout_fds[1], STDOUT_FILENO) } == -1 + || unsafe { dup2(pipe_stderr_fds[1], STDERR_FILENO) } == -1 + { + unsafe { + close(pipe_stdout_fds[0]); + close(pipe_stdout_fds[1]); + close(pipe_stderr_fds[0]); + close(pipe_stderr_fds[1]); } + return ( + "Failed to redirect STDOUT_FILENO or STDERR_FILENO".to_string(), + "".to_string(), + -1, + ); } uumain_exit_status = uumain_function(args.to_owned().into_iter()); io::stdout().flush().unwrap(); - - // Restore the original stdout + io::stderr().flush().unwrap(); + + // Restore the original stdout and stderr + if unsafe { dup2(original_stdout_fd, STDOUT_FILENO) } == -1 + || unsafe { dup2(original_stderr_fd, STDERR_FILENO) } == -1 + { + return ( + "Failed to restore the original STDOUT_FILENO or STDERR_FILENO".to_string(), + "".to_string(), + -1, + ); + } unsafe { - if dup2(original_stdout_fd, STDOUT_FILENO) == -1 { - return ( - "Failed to restore the original STDOUT_FILENO".to_string(), - -1, - ); - } close(original_stdout_fd); + close(original_stderr_fd); } - unsafe { close(pipe_fds[1]) }; + unsafe { close(pipe_stdout_fds[1]) }; + unsafe { close(pipe_stderr_fds[1]) }; + + let captured_stdout = read_from_fd(pipe_stdout_fds[0]).trim().to_string(); + let captured_stderr = read_from_fd(pipe_stderr_fds[0]).to_string(); + let captured_stderr = captured_stderr + .splitn(2, ':') + .nth(1) + .unwrap_or("") + .trim() + .to_string(); + + (captured_stdout, captured_stderr, uumain_exit_status) +} +fn read_from_fd(fd: RawFd) -> String { let mut captured_output = Vec::new(); let mut read_buffer = [0; 1024]; loop { let bytes_read = unsafe { libc::read( - pipe_fds[0], + fd, read_buffer.as_mut_ptr() as *mut libc::c_void, read_buffer.len(), ) @@ -93,29 +127,30 @@ where eprintln!("Failed to read from the pipe"); break; } - if bytes_read <= 0 { + if bytes_read == 0 { break; } captured_output.extend_from_slice(&read_buffer[..bytes_read as usize]); } - unsafe { libc::close(pipe_fds[0]) }; - - let my_output = String::from_utf8_lossy(&captured_output) - .to_string() - .trim() - .to_owned(); + unsafe { libc::close(fd) }; - (my_output, uumain_exit_status) + String::from_utf8_lossy(&captured_output).into_owned() } pub fn run_gnu_cmd( cmd_path: &str, args: &[OsString], check_gnu: bool, -) -> Result<(String, i32), io::Error> { +) -> Result<(String, String, i32), (String, String, i32)> { if check_gnu { - is_gnu_cmd(cmd_path)?; // Check if it's a GNU implementation + match is_gnu_cmd(cmd_path) { + Ok(_) => {} // if the check passes, do nothing + Err(e) => { + // Convert the io::Error into the function's error type + return Err((String::new(), e.to_string(), -1)); + } + } } let mut command = Command::new(cmd_path); @@ -123,17 +158,25 @@ pub fn run_gnu_cmd( command.arg(arg); } - let output = command.output()?; + let output = match command.output() { + Ok(output) => output, + Err(e) => return Err((String::new(), e.to_string(), -1)), + }; let exit_code = output.status.code().unwrap_or(-1); + + // Here we get stdout and stderr as Strings + let stdout = String::from_utf8_lossy(&output.stdout).to_string(); + let stderr = String::from_utf8_lossy(&output.stderr).to_string(); + let stderr = stderr + .splitn(2, ':') + .nth(1) + .unwrap_or("") + .trim() + .to_string(); + if output.status.success() || !check_gnu { - Ok(( - String::from_utf8_lossy(&output.stdout).to_string(), - exit_code, - )) + Ok((stdout, stderr, exit_code)) } else { - Err(io::Error::new( - io::ErrorKind::Other, - format!("GNU command execution failed with exit code {}", exit_code), - )) + Err((stdout, stderr, exit_code)) } } From 8ab20c4673dec1bc54bd8bc9a20177f6fffde16a Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Wed, 8 Nov 2023 23:31:41 +0100 Subject: [PATCH 0328/2851] fuzz: adjust the fuzzers with the new arg --- fuzz/fuzz_targets/fuzz_expr.rs | 33 ++++++++++++++++++++++++--------- fuzz/fuzz_targets/fuzz_test.rs | 29 +++++++++++++++++++++-------- 2 files changed, 45 insertions(+), 17 deletions(-) diff --git a/fuzz/fuzz_targets/fuzz_expr.rs b/fuzz/fuzz_targets/fuzz_expr.rs index c2217c48abc..ee65745bf2a 100644 --- a/fuzz/fuzz_targets/fuzz_expr.rs +++ b/fuzz/fuzz_targets/fuzz_expr.rs @@ -84,7 +84,7 @@ fuzz_target!(|_data: &[u8]| { let mut args = vec![OsString::from("expr")]; args.extend(expr.split_whitespace().map(OsString::from)); - let (rust_output, uumain_exit_code) = generate_and_run_uumain(&args, uumain); + let (rust_stdout, rust_stderr, uumain_exit_code) = generate_and_run_uumain(&args, uumain); // Use C locale to avoid false positives, like in https://github.com/uutils/coreutils/issues/5378, // because uutils expr doesn't support localization yet @@ -93,28 +93,43 @@ fuzz_target!(|_data: &[u8]| { // Run GNU expr with the provided arguments and compare the output match run_gnu_cmd(CMD_PATH, &args[1..], true) { - Ok((gnu_output, gnu_exit_code)) => { - let gnu_output = gnu_output.trim().to_owned(); + Ok((gnu_stdout, gnu_stderr, gnu_exit_code)) => { + let gnu_stdout = gnu_stdout.trim().to_owned(); if uumain_exit_code != gnu_exit_code { println!("Expression: {}", expr); + + println!("GNU stderr: {}", gnu_stderr); + println!("Rust stderr: {}", rust_stderr); + println!("Rust code: {}", uumain_exit_code); println!("GNU code: {}", gnu_exit_code); panic!("Different error codes"); } - if rust_output == gnu_output { + if rust_stdout == gnu_stdout { println!( "Outputs matched for expression: {} => Result: {}", - expr, rust_output + expr, rust_stdout ); } else { println!("Expression: {}", expr); - println!("Rust output: {}", rust_output); - println!("GNU output: {}", gnu_output); + println!("Rust output: {}", rust_stdout); + println!("GNU output: {}", gnu_stdout); panic!("Different output between Rust & GNU"); } } - Err(_) => { - println!("GNU expr execution failed for expression: {}", expr); + + Err((_gnu_stdout, gnu_stderr, _gnu_exit_code)) => { + if rust_stderr == gnu_stderr { + println!( + "GNU execution failed for input: {} stderr: {}", + expr, rust_stderr + ); + } else { + println!("Input: {}", expr); + println!("Rust stderr: {}", rust_stderr); + println!("GNU stderr: {}", gnu_stderr); + panic!("Different stderr between Rust & GNU"); + } } } }); diff --git a/fuzz/fuzz_targets/fuzz_test.rs b/fuzz/fuzz_targets/fuzz_test.rs index 4805a41af16..4c4834bdbaf 100644 --- a/fuzz/fuzz_targets/fuzz_test.rs +++ b/fuzz/fuzz_targets/fuzz_test.rs @@ -204,19 +204,22 @@ fuzz_target!(|_data: &[u8]| { args.push(OsString::from(generate_test_arg())); } - let (rust_output, uumain_exit_status) = generate_and_run_uumain(&args, uumain); + let (rust_stdout, rust_stderr, uumain_exit_status) = generate_and_run_uumain(&args, uumain); // Run GNU test with the provided arguments and compare the output match run_gnu_cmd(CMD_PATH, &args[1..], false) { - Ok((gnu_output, gnu_exit_status)) => { - let gnu_output = gnu_output.trim().to_owned(); + Ok((gnu_stdout, gnu_stderr, gnu_exit_status)) => { + let gnu_stdout = gnu_stdout.trim().to_owned(); println!("gnu_exit_status {}", gnu_exit_status); println!("uumain_exit_status {}", uumain_exit_status); - if rust_output != gnu_output || uumain_exit_status != gnu_exit_status { + if rust_stdout != gnu_stdout || uumain_exit_status != gnu_exit_status { println!("Discrepancy detected!"); println!("Test: {:?}", &args[1..]); - println!("My output: {}", rust_output); - println!("GNU output: {}", gnu_output); + println!("Rust output: {}", rust_stdout); + println!("GNU output: {}", gnu_stdout); + + println!("Rust stderr: {}", rust_stderr); + println!("GNU stderr: {}", gnu_stderr); println!("My exit status: {}", uumain_exit_status); println!("GNU exit status: {}", gnu_exit_status); panic!(); @@ -227,8 +230,18 @@ fuzz_target!(|_data: &[u8]| { ); } } - Err(_) => { - println!("GNU test execution failed for expression {:?}", &args[1..]); + Err((_gnu_stdout, gnu_stderr, _gnu_exit_code)) => { + if rust_stderr == gnu_stderr { + println!( + "GNU execution failed for input: {:?} stderr: {}", + args, rust_stderr + ); + } else { + println!("Input: {:?}", args); + println!("Rust stderr: {}", rust_stderr); + println!("GNU stderr: {}", gnu_stderr); + panic!("Different stderr between Rust & GNU"); + } } } }); From 198f7c7f26c6aa5a374d8f4def4ad324bee38535 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Thu, 9 Nov 2023 15:45:44 +0100 Subject: [PATCH 0329/2851] printf: move number formatting to separate module --- src/uucore/src/lib/features/format/mod.rs | 2 +- .../src/lib/features/format/num_format.rs | 232 ++++++++++++++++++ src/uucore/src/lib/features/format/spec.rs | 202 ++------------- 3 files changed, 258 insertions(+), 178 deletions(-) create mode 100644 src/uucore/src/lib/features/format/num_format.rs diff --git a/src/uucore/src/lib/features/format/mod.rs b/src/uucore/src/lib/features/format/mod.rs index ebb1cc360eb..0849ada15f8 100644 --- a/src/uucore/src/lib/features/format/mod.rs +++ b/src/uucore/src/lib/features/format/mod.rs @@ -10,8 +10,8 @@ //! parsing errors occur during writing. // spell-checker:ignore (vars) charf decf floatf intf scif strf Cninety -// mod num_format; mod spec; +mod num_format; use spec::Spec; use std::{ diff --git a/src/uucore/src/lib/features/format/num_format.rs b/src/uucore/src/lib/features/format/num_format.rs new file mode 100644 index 00000000000..75c18438cc8 --- /dev/null +++ b/src/uucore/src/lib/features/format/num_format.rs @@ -0,0 +1,232 @@ +use std::io::Write; + +use super::{ + spec::{ + Case, FloatVariant, ForceDecimal, NumberAlignment, PositiveSign, Prefix, UnsignedIntVariant, + }, + FormatError, +}; + +pub trait Formatter { + type Input; + fn fmt(&self, writer: impl Write, x: Self::Input) -> Result<(), FormatError>; +} + +pub struct SignedInt { + pub width: usize, + pub positive_sign: PositiveSign, + pub alignment: NumberAlignment, +} + +impl Formatter for SignedInt { + type Input = i64; + + fn fmt(&self, mut writer: impl Write, x: Self::Input) -> Result<(), FormatError> { + if x >= 0 { + match self.positive_sign { + PositiveSign::None => Ok(()), + PositiveSign::Plus => write!(writer, "+"), + PositiveSign::Space => write!(writer, " "), + } + .map_err(FormatError::IoError)?; + } + + match self.alignment { + NumberAlignment::Left => write!(writer, "{x: write!(writer, "{x:>width$}", width = self.width), + NumberAlignment::RightZero => write!(writer, "{x:0>width$}", width = self.width), + } + .map_err(FormatError::IoError) + } +} + +pub struct UnsignedInt { + pub variant: UnsignedIntVariant, + pub width: usize, + pub alignment: NumberAlignment, +} + +impl Formatter for UnsignedInt { + type Input = u64; + + fn fmt(&self, mut writer: impl Write, x: Self::Input) -> Result<(), FormatError> { + let s = match self.variant { + UnsignedIntVariant::Decimal => format!("{x}"), + UnsignedIntVariant::Octal(Prefix::No) => format!("{x:o}"), + UnsignedIntVariant::Octal(Prefix::Yes) => format!("{x:#o}"), + UnsignedIntVariant::Hexadecimal(Case::Lowercase, Prefix::No) => { + format!("{x:x}") + } + UnsignedIntVariant::Hexadecimal(Case::Lowercase, Prefix::Yes) => { + format!("{x:#x}") + } + UnsignedIntVariant::Hexadecimal(Case::Uppercase, Prefix::No) => { + format!("{x:X}") + } + UnsignedIntVariant::Hexadecimal(Case::Uppercase, Prefix::Yes) => { + format!("{x:#X}") + } + }; + + match self.alignment { + NumberAlignment::Left => write!(writer, "{s: write!(writer, "{s:>width$}", width = self.width), + NumberAlignment::RightZero => write!(writer, "{s:0>width$}", width = self.width), + } + .map_err(FormatError::IoError) + } +} + +pub struct Float { + pub variant: FloatVariant, + pub case: Case, + pub force_decimal: ForceDecimal, + pub width: usize, + pub positive_sign: PositiveSign, + pub alignment: NumberAlignment, + pub precision: usize, +} + +impl Formatter for Float { + type Input = f64; + + fn fmt(&self, mut writer: impl Write, x: Self::Input) -> Result<(), FormatError> { + if x.is_sign_positive() { + match self.positive_sign { + PositiveSign::None => Ok(()), + PositiveSign::Plus => write!(writer, "+"), + PositiveSign::Space => write!(writer, " "), + } + .map_err(FormatError::IoError)?; + } + + let s = match self.variant { + FloatVariant::Decimal => { + format_float_decimal(x, self.precision, self.case, self.force_decimal) + } + FloatVariant::Scientific => { + format_float_scientific(x, self.precision, self.case, self.force_decimal) + } + FloatVariant::Shortest => { + format_float_shortest(x, self.precision, self.case, self.force_decimal) + } + FloatVariant::Hexadecimal => { + format_float_hexadecimal(x, self.precision, self.case, self.force_decimal) + } + }; + + match self.alignment { + NumberAlignment::Left => write!(writer, "{s: write!(writer, "{s:>width$}", width = self.width), + NumberAlignment::RightZero => write!(writer, "{s:0>width$}", width = self.width), + } + .map_err(FormatError::IoError) + } +} + +fn format_float_nonfinite(f: f64, case: Case) -> String { + debug_assert!(!f.is_finite()); + let mut s = format!("{f}"); + if case == Case::Uppercase { + s.make_ascii_uppercase(); + } + return s; +} + +fn format_float_decimal( + f: f64, + precision: usize, + case: Case, + force_decimal: ForceDecimal, +) -> String { + if !f.is_finite() { + return format_float_nonfinite(f, case); + } + + if precision == 0 && force_decimal == ForceDecimal::Yes { + format!("{f:.0}.") + } else { + format!("{f:.*}", precision) + } +} + +fn format_float_scientific( + f: f64, + precision: usize, + case: Case, + force_decimal: ForceDecimal, +) -> String { + // If the float is NaN, -Nan, Inf or -Inf, format like any other float + if !f.is_finite() { + return format_float_nonfinite(f, case); + } + + let exponent: i32 = f.log10().floor() as i32; + let normalized = f / 10.0_f64.powi(exponent); + + let additional_dot = if precision == 0 && ForceDecimal::Yes == force_decimal { + "." + } else { + "" + }; + + let exp_char = match case { + Case::Lowercase => 'e', + Case::Uppercase => 'E', + }; + + format!( + "{normalized:.*}{additional_dot}{exp_char}{exponent:+03}", + precision + ) +} + +// TODO: This could be optimized. It's not terribly important though. +fn format_float_shortest( + f: f64, + precision: usize, + case: Case, + force_decimal: ForceDecimal, +) -> String { + let a = format_float_decimal(f, precision, case, force_decimal); + let b = format_float_scientific(f, precision, case, force_decimal); + + if a.len() > b.len() { + b + } else { + a + } +} + +fn format_float_hexadecimal( + f: f64, + precision: usize, + case: Case, + force_decimal: ForceDecimal, +) -> String { + if !f.is_finite() { + return format_float_nonfinite(f, case); + } + + let (first_digit, mantissa, exponent) = if f == 0.0 { + (0, 0, 0) + } else { + let bits = f.to_bits(); + let exponent_bits = ((bits >> 52) & 0x7fff) as i64; + let exponent = exponent_bits - 1023; + let mantissa = bits & 0xf_ffff_ffff_ffff; + (1, mantissa, exponent) + }; + + let mut s = match (precision, force_decimal) { + (0, ForceDecimal::No) => format!("0x{first_digit}p{exponent:+x}"), + (0, ForceDecimal::Yes) => format!("0x{first_digit}.p{exponent:+x}"), + _ => format!("0x{first_digit}.{mantissa:0>13x}p{exponent:+x}"), + }; + + if case == Case::Uppercase { + s.make_ascii_uppercase(); + } + + return s; +} diff --git a/src/uucore/src/lib/features/format/spec.rs b/src/uucore/src/lib/features/format/spec.rs index e66cad32d65..4a533d1e10f 100644 --- a/src/uucore/src/lib/features/format/spec.rs +++ b/src/uucore/src/lib/features/format/spec.rs @@ -1,6 +1,9 @@ // spell-checker:ignore (vars) charf decf floatf intf scif strf Cninety -use super::{FormatArgument, FormatError}; +use super::{ + num_format::{self, Formatter}, + FormatArgument, FormatError, +}; use std::{fmt::Display, io::Write}; pub enum Spec { @@ -256,7 +259,7 @@ impl Spec { pub fn write<'a>( &self, - mut writer: impl Write, + writer: impl Write, mut args: impl Iterator, ) -> Result<(), FormatError> { match self { @@ -288,21 +291,11 @@ impl Spec { return Err(FormatError::InvalidArgument(arg.clone())); }; - if *i >= 0 { - match positive_sign { - PositiveSign::None => Ok(()), - PositiveSign::Plus => write!(writer, "+"), - PositiveSign::Space => write!(writer, " "), - } - .map_err(FormatError::IoError)?; - } - - match alignment { - NumberAlignment::Left => write!(writer, "{i: write!(writer, "{i:>width$}"), - NumberAlignment::RightZero => write!(writer, "{i:0>width$}"), - } - .map_err(FormatError::IoError) + num_format::SignedInt { + width, + positive_sign, + alignment, + }.fmt(writer, *i) } &Spec::UnsignedInt { variant, @@ -312,34 +305,16 @@ impl Spec { let width = resolve_asterisk(width, &mut args)?.unwrap_or(0); let arg = next_arg(args)?; - let FormatArgument::SignedInt(i) = arg else { + let FormatArgument::UnsignedInt(i) = arg else { return Err(FormatError::InvalidArgument(arg.clone())); }; - let s = match variant { - UnsignedIntVariant::Decimal => format!("{i}"), - UnsignedIntVariant::Octal(Prefix::No) => format!("{i:o}"), - UnsignedIntVariant::Octal(Prefix::Yes) => format!("{i:#o}"), - UnsignedIntVariant::Hexadecimal(Case::Lowercase, Prefix::No) => { - format!("{i:x}") - } - UnsignedIntVariant::Hexadecimal(Case::Lowercase, Prefix::Yes) => { - format!("{i:#x}") - } - UnsignedIntVariant::Hexadecimal(Case::Uppercase, Prefix::No) => { - format!("{i:X}") - } - UnsignedIntVariant::Hexadecimal(Case::Uppercase, Prefix::Yes) => { - format!("{i:#X}") - } - }; - - match alignment { - NumberAlignment::Left => write!(writer, "{s: write!(writer, "{s:>width$}"), - NumberAlignment::RightZero => write!(writer, "{s:0>width$}"), + num_format::UnsignedInt { + variant, + width, + alignment, } - .map_err(FormatError::IoError) + .fmt(writer, *i) } &Spec::Float { variant, @@ -358,148 +333,21 @@ impl Spec { return Err(FormatError::InvalidArgument(arg.clone())); }; - if f.is_sign_positive() { - match positive_sign { - PositiveSign::None => Ok(()), - PositiveSign::Plus => write!(writer, "+"), - PositiveSign::Space => write!(writer, " "), - } - .map_err(FormatError::IoError)?; - } - - let s = match variant { - FloatVariant::Decimal => { - format_float_decimal(*f, precision, case, force_decimal) - } - FloatVariant::Scientific => { - format_float_scientific(*f, precision, case, force_decimal) - } - FloatVariant::Shortest => { - format_float_shortest(*f, precision, case, force_decimal) - } - FloatVariant::Hexadecimal => { - format_float_hexadecimal(*f, precision, case, force_decimal) - } - }; - - match alignment { - NumberAlignment::Left => write!(writer, "{s: write!(writer, "{s:>width$}"), - NumberAlignment::RightZero => write!(writer, "{s:0>width$}"), + num_format::Float { + variant, + case, + force_decimal, + width, + positive_sign, + alignment, + precision, } - .map_err(FormatError::IoError) + .fmt(writer, *f) } } } } -fn format_float_nonfinite(f: f64, case: Case) -> String { - debug_assert!(!f.is_finite()); - let mut s = format!("{f}"); - if case == Case::Uppercase { - s.make_ascii_uppercase(); - } - return s; -} - -fn format_float_decimal( - f: f64, - precision: usize, - case: Case, - force_decimal: ForceDecimal, -) -> String { - if !f.is_finite() { - return format_float_nonfinite(f, case); - } - - if precision == 0 && force_decimal == ForceDecimal::Yes { - format!("{f:.0}.") - } else { - format!("{f:.*}", precision) - } -} - -fn format_float_scientific( - f: f64, - precision: usize, - case: Case, - force_decimal: ForceDecimal, -) -> String { - // If the float is NaN, -Nan, Inf or -Inf, format like any other float - if !f.is_finite() { - return format_float_nonfinite(f, case); - } - - let exponent: i32 = f.log10().floor() as i32; - let normalized = f / 10.0_f64.powi(exponent); - - let additional_dot = if precision == 0 && ForceDecimal::Yes == force_decimal { - "." - } else { - "" - }; - - let exp_char = match case { - Case::Lowercase => 'e', - Case::Uppercase => 'E', - }; - - format!( - "{normalized:.*}{additional_dot}{exp_char}{exponent:+03}", - precision - ) -} - -// TODO: This could be optimized. It's not terribly important though. -fn format_float_shortest( - f: f64, - precision: usize, - case: Case, - force_decimal: ForceDecimal, -) -> String { - let a = format_float_decimal(f, precision, case, force_decimal); - let b = format_float_scientific(f, precision, case, force_decimal); - - if a.len() > b.len() { - b - } else { - a - } -} - -fn format_float_hexadecimal( - f: f64, - precision: usize, - case: Case, - force_decimal: ForceDecimal, -) -> String { - if !f.is_finite() { - return format_float_nonfinite(f, case); - } - - let (first_digit, mantissa, exponent) = if f == 0.0 { - (0, 0, 0) - } else { - let bits = f.to_bits(); - let exponent_bits = ((bits >> 52) & 0x7fff) as i64; - let exponent = exponent_bits - 1023; - let mantissa = bits & 0xf_ffff_ffff_ffff; - (1, mantissa, exponent) - }; - - let mut s = match (precision, force_decimal) { - (0, ForceDecimal::No) => format!("0x{first_digit}p{exponent:+x}"), - (0, ForceDecimal::Yes) => format!("0x{first_digit}.p{exponent:+x}"), - _ => format!("0x{first_digit}.{mantissa:0>13x}p{exponent:+x}"), - }; - - if case == Case::Uppercase { - s.make_ascii_uppercase(); - } - - return s; -} - fn resolve_asterisk<'a>( option: Option>, args: impl Iterator, From 39c675847545b5653e04f9338815d882fdc7d01a Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Thu, 9 Nov 2023 16:05:11 +0100 Subject: [PATCH 0330/2851] uucore/format: move types for num_format --- src/uucore/src/lib/features/format/mod.rs | 2 +- .../src/lib/features/format/num_format.rs | 69 +++++++++++++++++-- src/uucore/src/lib/features/format/spec.rs | 56 ++------------- 3 files changed, 70 insertions(+), 57 deletions(-) diff --git a/src/uucore/src/lib/features/format/mod.rs b/src/uucore/src/lib/features/format/mod.rs index 0849ada15f8..d6db5e8c7cd 100644 --- a/src/uucore/src/lib/features/format/mod.rs +++ b/src/uucore/src/lib/features/format/mod.rs @@ -11,7 +11,7 @@ // spell-checker:ignore (vars) charf decf floatf intf scif strf Cninety mod spec; -mod num_format; +pub mod num_format; use spec::Spec; use std::{ diff --git a/src/uucore/src/lib/features/format/num_format.rs b/src/uucore/src/lib/features/format/num_format.rs index 75c18438cc8..3a27ac200f8 100644 --- a/src/uucore/src/lib/features/format/num_format.rs +++ b/src/uucore/src/lib/features/format/num_format.rs @@ -1,17 +1,60 @@ use std::io::Write; -use super::{ - spec::{ - Case, FloatVariant, ForceDecimal, NumberAlignment, PositiveSign, Prefix, UnsignedIntVariant, - }, - FormatError, -}; +use super::FormatError; pub trait Formatter { type Input; fn fmt(&self, writer: impl Write, x: Self::Input) -> Result<(), FormatError>; } +#[derive(Clone, Copy)] +pub enum UnsignedIntVariant { + Decimal, + Octal(Prefix), + Hexadecimal(Case, Prefix), +} + +#[derive(Clone, Copy)] + +pub enum FloatVariant { + Decimal, + Scientific, + Shortest, + Hexadecimal, +} + +#[derive(Clone, Copy, PartialEq, Eq)] +pub enum Case { + Lowercase, + Uppercase, +} + +#[derive(Clone, Copy, PartialEq, Eq)] +pub enum Prefix { + No, + Yes, +} + +#[derive(Clone, Copy, PartialEq, Eq)] +pub enum ForceDecimal { + No, + Yes, +} + +#[derive(Clone, Copy)] +pub enum PositiveSign { + None, + Plus, + Space, +} + +#[derive(Clone, Copy)] +pub enum NumberAlignment { + Left, + RightSpace, + RightZero, +} + pub struct SignedInt { pub width: usize, pub positive_sign: PositiveSign, @@ -87,6 +130,20 @@ pub struct Float { pub precision: usize, } +impl Default for Float { + fn default() -> Self { + Self { + variant: FloatVariant::Decimal, + case: Case::Lowercase, + force_decimal: ForceDecimal::No, + width: 0, + positive_sign: PositiveSign::None, + alignment: NumberAlignment::Left, + precision: 2, + } + } +} + impl Formatter for Float { type Input = f64; diff --git a/src/uucore/src/lib/features/format/spec.rs b/src/uucore/src/lib/features/format/spec.rs index 4a533d1e10f..80896997007 100644 --- a/src/uucore/src/lib/features/format/spec.rs +++ b/src/uucore/src/lib/features/format/spec.rs @@ -1,7 +1,10 @@ // spell-checker:ignore (vars) charf decf floatf intf scif strf Cninety use super::{ - num_format::{self, Formatter}, + num_format::{ + self, Case, FloatVariant, ForceDecimal, Formatter, NumberAlignment, PositiveSign, Prefix, + UnsignedIntVariant, + }, FormatArgument, FormatError, }; use std::{fmt::Display, io::Write}; @@ -36,54 +39,6 @@ pub enum Spec { }, } -#[derive(Clone, Copy)] -pub enum UnsignedIntVariant { - Decimal, - Octal(Prefix), - Hexadecimal(Case, Prefix), -} - -#[derive(Clone, Copy)] - -pub enum FloatVariant { - Decimal, - Scientific, - Shortest, - Hexadecimal, -} - -#[derive(Clone, Copy, PartialEq, Eq)] -pub enum Case { - Lowercase, - Uppercase, -} - -#[derive(Clone, Copy, PartialEq, Eq)] -pub enum Prefix { - No, - Yes, -} - -#[derive(Clone, Copy, PartialEq, Eq)] -pub enum ForceDecimal { - No, - Yes, -} - -#[derive(Clone, Copy)] -pub enum PositiveSign { - None, - Plus, - Space, -} - -#[derive(Clone, Copy)] -pub enum NumberAlignment { - Left, - RightSpace, - RightZero, -} - /// Precision and width specified might use an asterisk to indicate that they are /// determined by an argument. #[derive(Clone, Copy)] @@ -295,7 +250,8 @@ impl Spec { width, positive_sign, alignment, - }.fmt(writer, *i) + } + .fmt(writer, *i) } &Spec::UnsignedInt { variant, From ee0e2c042bf93062727cf20356d4ebbfaa018291 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Thu, 9 Nov 2023 16:05:38 +0100 Subject: [PATCH 0331/2851] dd: use num_format::Float directly instead of printf --- src/uu/dd/src/progress.rs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/uu/dd/src/progress.rs b/src/uu/dd/src/progress.rs index 1d9b7247d20..269ae5df463 100644 --- a/src/uu/dd/src/progress.rs +++ b/src/uu/dd/src/progress.rs @@ -13,8 +13,10 @@ use std::io::Write; use std::sync::mpsc; use std::time::Duration; -use uucore::format::sprintf; -use uucore::{error::UResult, format::FormatArgument}; +use uucore::{ + error::UResult, + format::num_format::{FloatVariant, Formatter}, +}; use crate::numbers::{to_magnitude_and_suffix, SuffixType}; @@ -152,8 +154,13 @@ impl ProgUpdate { let (carriage_return, newline) = if rewrite { ("\r", "") } else { ("", "\n") }; // The duration should be formatted as in `printf %g`. - // TODO: remove unwrap and make FormatError implement UError - let duration_str = sprintf("%g", &[FormatArgument::Float(duration)])?; + let mut duration_str = Vec::new(); + uucore::format::num_format::Float { + variant: FloatVariant::Shortest, + ..Default::default() + } + .fmt(&mut duration_str, duration)?; + // We assume that printf will output valid UTF-8 let duration_str = std::str::from_utf8(&duration_str).unwrap(); // If the number of bytes written is sufficiently large, then From 12e61d451ce130876a9871aea94819b7f2dfe333 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Mon, 6 Nov 2023 16:34:52 +0100 Subject: [PATCH 0332/2851] du: make -l/--count-links work --- src/uu/du/src/du.rs | 5 +++++ tests/by-util/test_du.rs | 27 +++++++++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/src/uu/du/src/du.rs b/src/uu/du/src/du.rs index 589524482e1..0efbd236df5 100644 --- a/src/uu/du/src/du.rs +++ b/src/uu/du/src/du.rs @@ -88,6 +88,7 @@ struct Options { separate_dirs: bool, one_file_system: bool, dereference: Deref, + count_links: bool, inodes: bool, verbose: bool, } @@ -336,6 +337,9 @@ fn du( if let Some(inode) = this_stat.inode { if inodes.contains(&inode) { + if options.count_links { + my_stat.inodes += 1; + } continue; } inodes.insert(inode); @@ -561,6 +565,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } else { Deref::None }, + count_links: matches.get_flag(options::COUNT_LINKS), inodes: matches.get_flag(options::INODES), verbose: matches.get_flag(options::VERBOSE), }; diff --git a/tests/by-util/test_du.rs b/tests/by-util/test_du.rs index 37594217d44..c07de2851ee 100644 --- a/tests/by-util/test_du.rs +++ b/tests/by-util/test_du.rs @@ -441,6 +441,33 @@ fn test_du_inodes() { } } +#[test] +fn test_du_inodes_with_count_links() { + let ts = TestScenario::new(util_name!()); + let at = &ts.fixtures; + + at.mkdir("dir"); + at.touch("dir/file"); + at.hard_link("dir/file", "dir/hard_link_a"); + at.hard_link("dir/file", "dir/hard_link_b"); + + // ensure the hard links are not counted without --count-links + ts.ucmd() + .arg("--inodes") + .arg("dir") + .succeeds() + .stdout_is("2\tdir\n"); + + for arg in ["-l", "--count-links"] { + ts.ucmd() + .arg("--inodes") + .arg(arg) + .arg("dir") + .succeeds() + .stdout_is("4\tdir\n"); + } +} + #[test] fn test_du_h_flag_empty_file() { new_ucmd!() From 85777e6a4260f554b382c96431922e82bbac9fd5 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Thu, 9 Nov 2023 16:11:56 +0100 Subject: [PATCH 0333/2851] du: rename inodes -> seen_inodes --- src/uu/du/src/du.rs | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/uu/du/src/du.rs b/src/uu/du/src/du.rs index 0efbd236df5..148b197df33 100644 --- a/src/uu/du/src/du.rs +++ b/src/uu/du/src/du.rs @@ -296,7 +296,7 @@ fn du( mut my_stat: Stat, options: &Options, depth: usize, - inodes: &mut HashSet, + seen_inodes: &mut HashSet, exclude: &[Pattern], ) -> Box> { let mut stats = vec![]; @@ -336,13 +336,13 @@ fn du( } if let Some(inode) = this_stat.inode { - if inodes.contains(&inode) { + if seen_inodes.contains(&inode) { if options.count_links { my_stat.inodes += 1; } continue; } - inodes.insert(inode); + seen_inodes.insert(inode); } if this_stat.is_dir { if options.one_file_system { @@ -354,7 +354,13 @@ fn du( } } } - futures.push(du(this_stat, options, depth + 1, inodes, exclude)); + futures.push(du( + this_stat, + options, + depth + 1, + seen_inodes, + exclude, + )); } else { my_stat.size += this_stat.size; my_stat.blocks += this_stat.blocks; @@ -637,11 +643,11 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { // Check existence of path provided in argument if let Ok(stat) = Stat::new(&path, &options) { // Kick off the computation of disk usage from the initial path - let mut inodes: HashSet = HashSet::new(); + let mut seen_inodes: HashSet = HashSet::new(); if let Some(inode) = stat.inode { - inodes.insert(inode); + seen_inodes.insert(inode); } - let iter = du(stat, &options, 0, &mut inodes, &excludes); + let iter = du(stat, &options, 0, &mut seen_inodes, &excludes); // Sum up all the returned `Stat`s and display results let (_, len) = iter.size_hint(); From 104e707b07057d81d7be33b09d72b5d3f618b1d2 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Thu, 9 Nov 2023 00:04:14 +0100 Subject: [PATCH 0334/2851] fuzzing: provide a better error management --- fuzz/fuzz_targets/fuzz_common.rs | 66 ++++++++++++++++++++++++++++---- fuzz/fuzz_targets/fuzz_expr.rs | 59 ++++++++-------------------- fuzz/fuzz_targets/fuzz_test.rs | 58 ++++++++-------------------- 3 files changed, 92 insertions(+), 91 deletions(-) diff --git a/fuzz/fuzz_targets/fuzz_common.rs b/fuzz/fuzz_targets/fuzz_common.rs index 9afc2cc8332..4d796866697 100644 --- a/fuzz/fuzz_targets/fuzz_common.rs +++ b/fuzz/fuzz_targets/fuzz_common.rs @@ -38,8 +38,6 @@ pub fn generate_and_run_uumain(args: &[OsString], uumain_function: F) -> (Str where F: FnOnce(std::vec::IntoIter) -> i32, { - let uumain_exit_status; - // Duplicate the stdout and stderr file descriptors let original_stdout_fd = unsafe { dup(STDOUT_FILENO) }; let original_stderr_fd = unsafe { dup(STDERR_FILENO) }; @@ -78,7 +76,8 @@ where ); } - uumain_exit_status = uumain_function(args.to_owned().into_iter()); + let uumain_exit_status = uumain_function(args.to_owned().into_iter()); + io::stdout().flush().unwrap(); io::stderr().flush().unwrap(); @@ -102,8 +101,8 @@ where let captured_stdout = read_from_fd(pipe_stdout_fds[0]).trim().to_string(); let captured_stderr = read_from_fd(pipe_stderr_fds[0]).to_string(); let captured_stderr = captured_stderr - .splitn(2, ':') - .nth(1) + .split_once(':') + .map(|x| x.1) .unwrap_or("") .trim() .to_string(); @@ -168,8 +167,8 @@ pub fn run_gnu_cmd( let stdout = String::from_utf8_lossy(&output.stdout).to_string(); let stderr = String::from_utf8_lossy(&output.stderr).to_string(); let stderr = stderr - .splitn(2, ':') - .nth(1) + .split_once(':') + .map(|x| x.1) .unwrap_or("") .trim() .to_string(); @@ -180,3 +179,56 @@ pub fn run_gnu_cmd( Err((stdout, stderr, exit_code)) } } + +pub fn compare_result( + test_type: &str, + input: &str, + rust_stdout: &str, + gnu_stdout: &str, + rust_stderr: &str, + gnu_stderr: &str, + rust_exit_code: i32, + gnu_exit_code: i32, + fail_on_stderr_diff: bool, +) { + println!("Test Type: {}", test_type); + println!("Input: {}", input); + + let mut discrepancies = Vec::new(); + let mut should_panic = false; + + if rust_stdout.trim() != gnu_stdout.trim() { + discrepancies.push("stdout differs"); + println!("Rust stdout: {}", rust_stdout); + println!("GNU stdout: {}", gnu_stdout); + should_panic = true; + } + if rust_stderr.trim() != gnu_stderr.trim() { + discrepancies.push("stderr differs"); + println!("Rust stderr: {}", rust_stderr); + println!("GNU stderr: {}", gnu_stderr); + if fail_on_stderr_diff { + should_panic = true; + } + } + if rust_exit_code != gnu_exit_code { + discrepancies.push("exit code differs"); + println!("Rust exit code: {}", rust_exit_code); + println!("GNU exit code: {}", gnu_exit_code); + should_panic = true; + } + + if discrepancies.is_empty() { + println!("All outputs and exit codes matched."); + } else { + println!("Discrepancy detected: {}", discrepancies.join(", ")); + if should_panic { + panic!("Test failed for {}: {}", test_type, input); + } else { + println!( + "Test completed with discrepancies for {}: {}", + test_type, input + ); + } + } +} diff --git a/fuzz/fuzz_targets/fuzz_expr.rs b/fuzz/fuzz_targets/fuzz_expr.rs index ee65745bf2a..4f0ad3c4ada 100644 --- a/fuzz/fuzz_targets/fuzz_expr.rs +++ b/fuzz/fuzz_targets/fuzz_expr.rs @@ -13,7 +13,7 @@ use rand::Rng; use std::{env, ffi::OsString}; mod fuzz_common; -use crate::fuzz_common::{generate_and_run_uumain, run_gnu_cmd}; +use crate::fuzz_common::{compare_result, generate_and_run_uumain, run_gnu_cmd}; static CMD_PATH: &str = "expr"; @@ -84,52 +84,25 @@ fuzz_target!(|_data: &[u8]| { let mut args = vec![OsString::from("expr")]; args.extend(expr.split_whitespace().map(OsString::from)); - let (rust_stdout, rust_stderr, uumain_exit_code) = generate_and_run_uumain(&args, uumain); - // Use C locale to avoid false positives, like in https://github.com/uutils/coreutils/issues/5378, // because uutils expr doesn't support localization yet // TODO remove once uutils expr supports localization env::set_var("LC_COLLATE", "C"); - // Run GNU expr with the provided arguments and compare the output - match run_gnu_cmd(CMD_PATH, &args[1..], true) { - Ok((gnu_stdout, gnu_stderr, gnu_exit_code)) => { - let gnu_stdout = gnu_stdout.trim().to_owned(); - if uumain_exit_code != gnu_exit_code { - println!("Expression: {}", expr); - - println!("GNU stderr: {}", gnu_stderr); - println!("Rust stderr: {}", rust_stderr); - - println!("Rust code: {}", uumain_exit_code); - println!("GNU code: {}", gnu_exit_code); - panic!("Different error codes"); - } - if rust_stdout == gnu_stdout { - println!( - "Outputs matched for expression: {} => Result: {}", - expr, rust_stdout - ); - } else { - println!("Expression: {}", expr); - println!("Rust output: {}", rust_stdout); - println!("GNU output: {}", gnu_stdout); - panic!("Different output between Rust & GNU"); - } - } + let (rust_stdout, rust_stderr, uumain_exit_code) = generate_and_run_uumain(&args, uumain); - Err((_gnu_stdout, gnu_stderr, _gnu_exit_code)) => { - if rust_stderr == gnu_stderr { - println!( - "GNU execution failed for input: {} stderr: {}", - expr, rust_stderr - ); - } else { - println!("Input: {}", expr); - println!("Rust stderr: {}", rust_stderr); - println!("GNU stderr: {}", gnu_stderr); - panic!("Different stderr between Rust & GNU"); - } - } - } + let (gnu_stdout, gnu_stderr, gnu_exit_code) = + run_gnu_cmd(CMD_PATH, &args[1..], false).unwrap_or_else(|e| e); + + compare_result( + "expr", + &format!("{:?}", &args[1..]), + &rust_stdout, + &gnu_stdout, + &rust_stderr, + &gnu_stderr, + uumain_exit_code, + gnu_exit_code, + false, // Set to true if you want to fail on stderr diff + ); }); diff --git a/fuzz/fuzz_targets/fuzz_test.rs b/fuzz/fuzz_targets/fuzz_test.rs index 4c4834bdbaf..d112126663e 100644 --- a/fuzz/fuzz_targets/fuzz_test.rs +++ b/fuzz/fuzz_targets/fuzz_test.rs @@ -13,7 +13,7 @@ use rand::Rng; use std::ffi::OsString; mod fuzz_common; -use crate::fuzz_common::{generate_and_run_uumain, run_gnu_cmd}; +use crate::fuzz_common::{compare_result, generate_and_run_uumain, run_gnu_cmd}; #[derive(PartialEq, Debug, Clone)] enum ArgType { @@ -204,44 +204,20 @@ fuzz_target!(|_data: &[u8]| { args.push(OsString::from(generate_test_arg())); } - let (rust_stdout, rust_stderr, uumain_exit_status) = generate_and_run_uumain(&args, uumain); - - // Run GNU test with the provided arguments and compare the output - match run_gnu_cmd(CMD_PATH, &args[1..], false) { - Ok((gnu_stdout, gnu_stderr, gnu_exit_status)) => { - let gnu_stdout = gnu_stdout.trim().to_owned(); - println!("gnu_exit_status {}", gnu_exit_status); - println!("uumain_exit_status {}", uumain_exit_status); - if rust_stdout != gnu_stdout || uumain_exit_status != gnu_exit_status { - println!("Discrepancy detected!"); - println!("Test: {:?}", &args[1..]); - println!("Rust output: {}", rust_stdout); - println!("GNU output: {}", gnu_stdout); - - println!("Rust stderr: {}", rust_stderr); - println!("GNU stderr: {}", gnu_stderr); - println!("My exit status: {}", uumain_exit_status); - println!("GNU exit status: {}", gnu_exit_status); - panic!(); - } else { - println!( - "Outputs and exit statuses matched for expression {:?}", - &args[1..] - ); - } - } - Err((_gnu_stdout, gnu_stderr, _gnu_exit_code)) => { - if rust_stderr == gnu_stderr { - println!( - "GNU execution failed for input: {:?} stderr: {}", - args, rust_stderr - ); - } else { - println!("Input: {:?}", args); - println!("Rust stderr: {}", rust_stderr); - println!("GNU stderr: {}", gnu_stderr); - panic!("Different stderr between Rust & GNU"); - } - } - } + let (rust_stdout, rust_stderr, uumain_exit_code) = generate_and_run_uumain(&args, uumain); + + let (gnu_stdout, gnu_stderr, gnu_exit_code) = + run_gnu_cmd(CMD_PATH, &args[1..], false).unwrap_or_else(|e| e); + + compare_result( + "test", + &format!("{:?}", &args[1..]), + &rust_stdout, + &gnu_stdout, + &rust_stderr, + &gnu_stderr, + uumain_exit_code, + gnu_exit_code, + false, // Set to true if you want to fail on stderr diff + ); }); From 7a38284075889dad9781de70921dc5c694291220 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 9 Nov 2023 18:43:09 +0000 Subject: [PATCH 0335/2851] chore(deps): update rust crate bstr to 1.8 --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3a3e4b2c4d2..677dbe89d3d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -188,9 +188,9 @@ dependencies = [ [[package]] name = "bstr" -version = "1.7.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c79ad7fb2dd38f3dabd76b09c6a5a20c038fc0213ef1e9afd30eb777f120f019" +checksum = "542f33a8835a0884b006a0c3df3dadd99c0c3f296ed26c2fdc8028e01ad6230c" dependencies = [ "memchr", "regex-automata", diff --git a/Cargo.toml b/Cargo.toml index fac6f5e899a..7a96d9a4f22 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -259,7 +259,7 @@ test = ["uu_test"] [workspace.dependencies] bigdecimal = "0.4" binary-heap-plus = "0.5.0" -bstr = "1.7" +bstr = "1.8" bytecount = "0.6.7" byteorder = "1.5.0" chrono = { version = "^0.4.31", default-features = false, features = [ From a29ddea4e9ebf82b8c552bbdb79b6b0077900249 Mon Sep 17 00:00:00 2001 From: clint Date: Thu, 9 Nov 2023 15:58:12 -0500 Subject: [PATCH 0336/2851] expand: clarify tabstops argument name for `expand_line` --- src/uu/expand/src/expand.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/uu/expand/src/expand.rs b/src/uu/expand/src/expand.rs index 4815c5f683c..99b9d6b815d 100644 --- a/src/uu/expand/src/expand.rs +++ b/src/uu/expand/src/expand.rs @@ -366,10 +366,11 @@ enum CharType { Other, } +#[allow(clippy::cognitive_complexity)] fn expand_line( buf: &mut Vec, output: &mut BufWriter, - ts: &[usize], + tabstops: &[usize], options: &Options, ) -> std::io::Result<()> { use self::CharType::*; @@ -415,7 +416,7 @@ fn expand_line( match ctype { Tab => { // figure out how many spaces to the next tabstop - let nts = next_tabstop(ts, col, &options.remaining_mode); + let nts = next_tabstop(tabstops, col, &options.remaining_mode); col += nts; // now dump out either spaces if we're expanding, or a literal tab if we're not From 2746c199cf58cb8e4e9837fee299de5453396087 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Fri, 10 Nov 2023 16:01:38 +0100 Subject: [PATCH 0337/2851] use a command result structure --- fuzz/fuzz_targets/fuzz_common.rs | 90 +++++++++++++++++++++++--------- fuzz/fuzz_targets/fuzz_expr.rs | 34 +++++++----- fuzz/fuzz_targets/fuzz_test.rs | 32 ++++++++---- 3 files changed, 109 insertions(+), 47 deletions(-) diff --git a/fuzz/fuzz_targets/fuzz_common.rs b/fuzz/fuzz_targets/fuzz_common.rs index 4d796866697..0fe2306e751 100644 --- a/fuzz/fuzz_targets/fuzz_common.rs +++ b/fuzz/fuzz_targets/fuzz_common.rs @@ -12,6 +12,19 @@ use std::process::Command; use std::sync::atomic::Ordering; use std::sync::{atomic::AtomicBool, Once}; +/// Represents the result of running a command, including its standard output, +/// standard error, and exit code. +pub struct CommandResult { + /// The standard output (stdout) of the command as a string. + pub stdout: String, + + /// The standard error (stderr) of the command as a string. + pub stderr: String, + + /// The exit code of the command. + pub exit_code: i32, +} + static CHECK_GNU: Once = Once::new(); static IS_GNU: AtomicBool = AtomicBool::new(false); @@ -34,7 +47,7 @@ pub fn is_gnu_cmd(cmd_path: &str) -> Result<(), std::io::Error> { } } -pub fn generate_and_run_uumain(args: &[OsString], uumain_function: F) -> (String, String, i32) +pub fn generate_and_run_uumain(args: &[OsString], uumain_function: F) -> CommandResult where F: FnOnce(std::vec::IntoIter) -> i32, { @@ -42,11 +55,11 @@ where let original_stdout_fd = unsafe { dup(STDOUT_FILENO) }; let original_stderr_fd = unsafe { dup(STDERR_FILENO) }; if original_stdout_fd == -1 || original_stderr_fd == -1 { - return ( - "Failed to duplicate STDOUT_FILENO or STDERR_FILENO".to_string(), - "".to_string(), - -1, - ); + return CommandResult { + stdout: "Failed to duplicate STDOUT_FILENO or STDERR_FILENO".to_string(), + stderr: "".to_string(), + exit_code: -1, + }; } println!("Running test {:?}", &args[0..]); let mut pipe_stdout_fds = [-1; 2]; @@ -56,7 +69,11 @@ where if unsafe { pipe(pipe_stdout_fds.as_mut_ptr()) } == -1 || unsafe { pipe(pipe_stderr_fds.as_mut_ptr()) } == -1 { - return ("Failed to create pipes".to_string(), "".to_string(), -1); + return CommandResult { + stdout: "Failed to create pipes".to_string(), + stderr: "".to_string(), + exit_code: -1, + }; } // Redirect stdout and stderr to their respective pipes @@ -69,11 +86,11 @@ where close(pipe_stderr_fds[0]); close(pipe_stderr_fds[1]); } - return ( - "Failed to redirect STDOUT_FILENO or STDERR_FILENO".to_string(), - "".to_string(), - -1, - ); + return CommandResult { + stdout: "Failed to redirect STDOUT_FILENO or STDERR_FILENO".to_string(), + stderr: "".to_string(), + exit_code: -1, + }; } let uumain_exit_status = uumain_function(args.to_owned().into_iter()); @@ -85,18 +102,19 @@ where if unsafe { dup2(original_stdout_fd, STDOUT_FILENO) } == -1 || unsafe { dup2(original_stderr_fd, STDERR_FILENO) } == -1 { - return ( - "Failed to restore the original STDOUT_FILENO or STDERR_FILENO".to_string(), - "".to_string(), - -1, - ); + return CommandResult { + stdout: "Failed to restore the original STDOUT_FILENO or STDERR_FILENO".to_string(), + stderr: "".to_string(), + exit_code: -1, + }; } unsafe { close(original_stdout_fd); close(original_stderr_fd); + + close(pipe_stdout_fds[1]); + close(pipe_stderr_fds[1]); } - unsafe { close(pipe_stdout_fds[1]) }; - unsafe { close(pipe_stderr_fds[1]) }; let captured_stdout = read_from_fd(pipe_stdout_fds[0]).trim().to_string(); let captured_stderr = read_from_fd(pipe_stderr_fds[0]).to_string(); @@ -107,7 +125,11 @@ where .trim() .to_string(); - (captured_stdout, captured_stderr, uumain_exit_status) + CommandResult { + stdout: captured_stdout, + stderr: captured_stderr, + exit_code: uumain_exit_status, + } } fn read_from_fd(fd: RawFd) -> String { @@ -141,13 +163,17 @@ pub fn run_gnu_cmd( cmd_path: &str, args: &[OsString], check_gnu: bool, -) -> Result<(String, String, i32), (String, String, i32)> { +) -> Result { if check_gnu { match is_gnu_cmd(cmd_path) { Ok(_) => {} // if the check passes, do nothing Err(e) => { // Convert the io::Error into the function's error type - return Err((String::new(), e.to_string(), -1)); + return Err(CommandResult { + stdout: String::new(), + stderr: e.to_string(), + exit_code: -1, + }); } } } @@ -159,7 +185,13 @@ pub fn run_gnu_cmd( let output = match command.output() { Ok(output) => output, - Err(e) => return Err((String::new(), e.to_string(), -1)), + Err(e) => { + return Err(CommandResult { + stdout: String::new(), + stderr: e.to_string(), + exit_code: -1, + }); + } }; let exit_code = output.status.code().unwrap_or(-1); @@ -174,9 +206,17 @@ pub fn run_gnu_cmd( .to_string(); if output.status.success() || !check_gnu { - Ok((stdout, stderr, exit_code)) + Ok(CommandResult { + stdout, + stderr, + exit_code, + }) } else { - Err((stdout, stderr, exit_code)) + Err(CommandResult { + stdout, + stderr, + exit_code, + }) } } diff --git a/fuzz/fuzz_targets/fuzz_expr.rs b/fuzz/fuzz_targets/fuzz_expr.rs index 4f0ad3c4ada..90daa107fc8 100644 --- a/fuzz/fuzz_targets/fuzz_expr.rs +++ b/fuzz/fuzz_targets/fuzz_expr.rs @@ -13,8 +13,8 @@ use rand::Rng; use std::{env, ffi::OsString}; mod fuzz_common; +use crate::fuzz_common::CommandResult; use crate::fuzz_common::{compare_result, generate_and_run_uumain, run_gnu_cmd}; - static CMD_PATH: &str = "expr"; fn generate_random_string(max_length: usize) -> String { @@ -88,21 +88,31 @@ fuzz_target!(|_data: &[u8]| { // because uutils expr doesn't support localization yet // TODO remove once uutils expr supports localization env::set_var("LC_COLLATE", "C"); - - let (rust_stdout, rust_stderr, uumain_exit_code) = generate_and_run_uumain(&args, uumain); - - let (gnu_stdout, gnu_stderr, gnu_exit_code) = - run_gnu_cmd(CMD_PATH, &args[1..], false).unwrap_or_else(|e| e); + let rust_result = generate_and_run_uumain(&args, uumain); + + let gnu_result = match run_gnu_cmd(CMD_PATH, &args[1..], false) { + Ok(result) => result, + Err(error_result) => { + eprintln!("Failed to run GNU command:"); + eprintln!("Stderr: {}", error_result.stderr); + eprintln!("Exit Code: {}", error_result.exit_code); + CommandResult { + stdout: String::new(), + stderr: error_result.stderr, + exit_code: error_result.exit_code, + } + } + }; compare_result( "expr", &format!("{:?}", &args[1..]), - &rust_stdout, - &gnu_stdout, - &rust_stderr, - &gnu_stderr, - uumain_exit_code, - gnu_exit_code, + &rust_result.stdout, + &gnu_result.stdout, + &rust_result.stderr, + &gnu_result.stderr, + rust_result.exit_code, + gnu_result.exit_code, false, // Set to true if you want to fail on stderr diff ); }); diff --git a/fuzz/fuzz_targets/fuzz_test.rs b/fuzz/fuzz_targets/fuzz_test.rs index d112126663e..0f2328c55db 100644 --- a/fuzz/fuzz_targets/fuzz_test.rs +++ b/fuzz/fuzz_targets/fuzz_test.rs @@ -13,6 +13,7 @@ use rand::Rng; use std::ffi::OsString; mod fuzz_common; +use crate::fuzz_common::CommandResult; use crate::fuzz_common::{compare_result, generate_and_run_uumain, run_gnu_cmd}; #[derive(PartialEq, Debug, Clone)] @@ -204,20 +205,31 @@ fuzz_target!(|_data: &[u8]| { args.push(OsString::from(generate_test_arg())); } - let (rust_stdout, rust_stderr, uumain_exit_code) = generate_and_run_uumain(&args, uumain); - - let (gnu_stdout, gnu_stderr, gnu_exit_code) = - run_gnu_cmd(CMD_PATH, &args[1..], false).unwrap_or_else(|e| e); + let rust_result = generate_and_run_uumain(&args, uumain); + + let gnu_result = match run_gnu_cmd(CMD_PATH, &args[1..], false) { + Ok(result) => result, + Err(error_result) => { + eprintln!("Failed to run GNU command:"); + eprintln!("Stderr: {}", error_result.stderr); + eprintln!("Exit Code: {}", error_result.exit_code); + CommandResult { + stdout: String::new(), + stderr: error_result.stderr, + exit_code: error_result.exit_code, + } + } + }; compare_result( "test", &format!("{:?}", &args[1..]), - &rust_stdout, - &gnu_stdout, - &rust_stderr, - &gnu_stderr, - uumain_exit_code, - gnu_exit_code, + &rust_result.stdout, + &gnu_result.stdout, + &rust_result.stderr, + &gnu_result.stderr, + rust_result.exit_code, + gnu_result.exit_code, false, // Set to true if you want to fail on stderr diff ); }); From 24d7f1fe5cbfce05b5a7f6ec0625f80a2a3eaf83 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 10 Nov 2023 16:59:08 +0000 Subject: [PATCH 0338/2851] chore(deps): update rust crate self_cell to 1.0.2 --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 677dbe89d3d..2496bbb7b79 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1870,9 +1870,9 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "self_cell" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c309e515543e67811222dbc9e3dd7e1056279b782e1dacffe4242b718734fb6" +checksum = "e388332cd64eb80cd595a00941baf513caffae8dce9cfd0467fc9c66397dade6" [[package]] name = "selinux" diff --git a/Cargo.toml b/Cargo.toml index 7a96d9a4f22..b4a0dc07c26 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -313,7 +313,7 @@ regex = "1.10.2" rstest = "0.18.2" rust-ini = "0.19.0" same-file = "1.0.6" -self_cell = "1.0.1" +self_cell = "1.0.2" selinux = "0.4" signal-hook = "0.3.17" smallvec = { version = "1.11", features = ["union"] } From 70c48fca78a7df31d752e6c7a9112e9c5da93bf7 Mon Sep 17 00:00:00 2001 From: David Zbarsky Date: Sat, 11 Nov 2023 12:53:15 -0500 Subject: [PATCH 0339/2851] Add darwin arm64 builds --- .github/workflows/CICD.yml | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index 6583a009474..b158c9c4ac9 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -461,14 +461,15 @@ jobs: fail-fast: false matrix: job: - # { os , target , cargo-options , features , use-cross , toolchain } + # - { os , target , cargo-options , features , use-cross , toolchain, skip-tests } - { os: ubuntu-latest , target: arm-unknown-linux-gnueabihf, features: feat_os_unix_gnueabihf, use-cross: use-cross, } - { os: ubuntu-latest , target: aarch64-unknown-linux-gnu , features: feat_os_unix_gnueabihf , use-cross: use-cross } # - { os: ubuntu-latest , target: x86_64-unknown-linux-gnu , features: feat_selinux , use-cross: use-cross } - - { os: ubuntu-latest , target: i686-unknown-linux-gnu , features: feat_os_unix , use-cross: use-cross } - - { os: ubuntu-latest , target: i686-unknown-linux-musl , features: feat_os_unix_musl , use-cross: use-cross } - - { os: ubuntu-latest , target: x86_64-unknown-linux-gnu , features: feat_os_unix , use-cross: use-cross } - - { os: ubuntu-latest , target: x86_64-unknown-linux-musl , features: feat_os_unix_musl , use-cross: use-cross } + - { os: ubuntu-latest , target: i686-unknown-linux-gnu , features: feat_os_unix , use-cross: use-cross } + - { os: ubuntu-latest , target: i686-unknown-linux-musl , features: feat_os_unix_musl , use-cross: use-cross } + - { os: ubuntu-latest , target: x86_64-unknown-linux-gnu , features: feat_os_unix , use-cross: use-cross } + - { os: ubuntu-latest , target: x86_64-unknown-linux-musl , features: feat_os_unix_musl , use-cross: use-cross } + - { os: macos-latest , target: aarch64-apple-darwin , features: feat_os_macos , use-cross: use-cross, skip-tests: true} # Hopefully github provides free M1 runners soon... - { os: macos-latest , target: x86_64-apple-darwin , features: feat_os_macos } - { os: windows-latest , target: i686-pc-windows-msvc , features: feat_os_windows } - { os: windows-latest , target: x86_64-pc-windows-gnu , features: feat_os_windows } ## note: requires rust >= 1.43.0 to link correctly @@ -575,7 +576,7 @@ jobs: - uses: taiki-e/install-action@v2 if: steps.vars.outputs.CARGO_CMD == 'cross' with: - tool: cross@0.2.1 + tool: cross@0.2.5 - name: Create all needed build/work directories shell: bash run: | @@ -650,6 +651,7 @@ jobs: ${{ steps.vars.outputs.CARGO_CMD }} +${{ env.RUST_MIN_SRV }} build --release \ --target=${{ matrix.job.target }} ${{ matrix.job.cargo-options }} ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} - name: Test + if: matrix.job.skip-tests != true shell: bash run: | ## Test @@ -658,6 +660,7 @@ jobs: env: RUST_BACKTRACE: "1" - name: Test individual utilities + if: matrix.job.skip-tests != true shell: bash run: | ## Test individual utilities From fddf301a52e512ba4b20df4f2142a64c0560ba51 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 12 Nov 2023 10:25:55 +0100 Subject: [PATCH 0340/2851] fuzz: Move a duplicate function into fuzz_common --- fuzz/fuzz_targets/fuzz_common.rs | 25 +++++++++++++++++++++++++ fuzz/fuzz_targets/fuzz_expr.rs | 27 +++------------------------ fuzz/fuzz_targets/fuzz_test.rs | 27 +++------------------------ 3 files changed, 31 insertions(+), 48 deletions(-) diff --git a/fuzz/fuzz_targets/fuzz_common.rs b/fuzz/fuzz_targets/fuzz_common.rs index 0fe2306e751..2adbb3dd677 100644 --- a/fuzz/fuzz_targets/fuzz_common.rs +++ b/fuzz/fuzz_targets/fuzz_common.rs @@ -4,6 +4,8 @@ // file that was distributed with this source code. use libc::{close, dup, dup2, pipe, STDERR_FILENO, STDOUT_FILENO}; +use rand::prelude::SliceRandom; +use rand::Rng; use std::ffi::OsString; use std::io; use std::io::Write; @@ -272,3 +274,26 @@ pub fn compare_result( } } } + +pub fn generate_random_string(max_length: usize) -> String { + let mut rng = rand::thread_rng(); + let valid_utf8: Vec = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" + .chars() + .collect(); + let invalid_utf8 = [0xC3, 0x28]; // Invalid UTF-8 sequence + let mut result = String::new(); + + for _ in 0..rng.gen_range(1..=max_length) { + if rng.gen_bool(0.9) { + let ch = valid_utf8.choose(&mut rng).unwrap(); + result.push(*ch); + } else { + let ch = invalid_utf8.choose(&mut rng).unwrap(); + if let Some(c) = char::from_u32(*ch as u32) { + result.push(c); + } + } + } + + result +} diff --git a/fuzz/fuzz_targets/fuzz_expr.rs b/fuzz/fuzz_targets/fuzz_expr.rs index 90daa107fc8..8d1848545ff 100644 --- a/fuzz/fuzz_targets/fuzz_expr.rs +++ b/fuzz/fuzz_targets/fuzz_expr.rs @@ -14,32 +14,11 @@ use std::{env, ffi::OsString}; mod fuzz_common; use crate::fuzz_common::CommandResult; -use crate::fuzz_common::{compare_result, generate_and_run_uumain, run_gnu_cmd}; +use crate::fuzz_common::{ + compare_result, generate_and_run_uumain, generate_random_string, run_gnu_cmd, +}; static CMD_PATH: &str = "expr"; -fn generate_random_string(max_length: usize) -> String { - let mut rng = rand::thread_rng(); - let valid_utf8: Vec = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" - .chars() - .collect(); - let invalid_utf8 = [0xC3, 0x28]; // Invalid UTF-8 sequence - let mut result = String::new(); - - for _ in 0..rng.gen_range(1..=max_length) { - if rng.gen_bool(0.9) { - let ch = valid_utf8.choose(&mut rng).unwrap(); - result.push(*ch); - } else { - let ch = invalid_utf8.choose(&mut rng).unwrap(); - if let Some(c) = char::from_u32(*ch as u32) { - result.push(c); - } - } - } - - result -} - fn generate_expr(max_depth: u32) -> String { let mut rng = rand::thread_rng(); let ops = ["+", "-", "*", "/", "%", "<", ">", "=", "&", "|"]; diff --git a/fuzz/fuzz_targets/fuzz_test.rs b/fuzz/fuzz_targets/fuzz_test.rs index 0f2328c55db..38cd691b389 100644 --- a/fuzz/fuzz_targets/fuzz_test.rs +++ b/fuzz/fuzz_targets/fuzz_test.rs @@ -14,7 +14,9 @@ use std::ffi::OsString; mod fuzz_common; use crate::fuzz_common::CommandResult; -use crate::fuzz_common::{compare_result, generate_and_run_uumain, run_gnu_cmd}; +use crate::fuzz_common::{ + compare_result, generate_and_run_uumain, generate_random_string, run_gnu_cmd, +}; #[derive(PartialEq, Debug, Clone)] enum ArgType { @@ -29,29 +31,6 @@ enum ArgType { static CMD_PATH: &str = "test"; -fn generate_random_string(max_length: usize) -> String { - let mut rng = rand::thread_rng(); - let valid_utf8: Vec = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" - .chars() - .collect(); - let invalid_utf8 = [0xC3, 0x28]; // Invalid UTF-8 sequence - let mut result = String::new(); - - for _ in 0..rng.gen_range(1..=max_length) { - if rng.gen_bool(0.9) { - let ch = valid_utf8.choose(&mut rng).unwrap(); - result.push(*ch); - } else { - let ch = invalid_utf8.choose(&mut rng).unwrap(); - if let Some(c) = char::from_u32(*ch as u32) { - result.push(c); - } - } - } - - result -} - #[derive(Debug, Clone)] struct TestArg { arg: String, From cdbc7bb416ac74bbae65d509decf7de633ac4636 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Sun, 12 Nov 2023 13:41:29 +0100 Subject: [PATCH 0341/2851] ci: remove outdated comment --- .github/workflows/CICD.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index b158c9c4ac9..66ee23168b8 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -472,7 +472,7 @@ jobs: - { os: macos-latest , target: aarch64-apple-darwin , features: feat_os_macos , use-cross: use-cross, skip-tests: true} # Hopefully github provides free M1 runners soon... - { os: macos-latest , target: x86_64-apple-darwin , features: feat_os_macos } - { os: windows-latest , target: i686-pc-windows-msvc , features: feat_os_windows } - - { os: windows-latest , target: x86_64-pc-windows-gnu , features: feat_os_windows } ## note: requires rust >= 1.43.0 to link correctly + - { os: windows-latest , target: x86_64-pc-windows-gnu , features: feat_os_windows } - { os: windows-latest , target: x86_64-pc-windows-msvc , features: feat_os_windows } steps: - uses: actions/checkout@v4 From 6481d63ea4b8cd768d064e0a6769d7cbd4a2803c Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Mon, 13 Nov 2023 15:22:49 +0100 Subject: [PATCH 0342/2851] uucore/format: implement single specifier formats --- src/uu/seq/src/seq.rs | 58 ++++++--- src/uucore/src/lib/features/format/mod.rs | 72 +++++++++++- .../src/lib/features/format/num_format.rs | 111 ++++++++++++++++-- src/uucore/src/lib/features/format/spec.rs | 3 + 4 files changed, 216 insertions(+), 28 deletions(-) diff --git a/src/uu/seq/src/seq.rs b/src/uu/seq/src/seq.rs index 217e9042833..bb4d5414ef8 100644 --- a/src/uu/seq/src/seq.rs +++ b/src/uu/seq/src/seq.rs @@ -3,13 +3,13 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. // spell-checker:ignore (ToDO) istr chiter argptr ilen extendedbigdecimal extendedbigint numberparse -use std::io::{stdout, ErrorKind, Write}; +use std::io::{stdout, Write}; use clap::{crate_version, Arg, ArgAction, Command}; -use num_traits::Zero; +use num_traits::{Zero, ToPrimitive}; use uucore::error::UResult; -use uucore::format::{printf, FormatArgument}; +use uucore::format::{printf, FormatArgument, Format, num_format}; use uucore::{format_usage, help_about, help_usage}; mod error; @@ -119,16 +119,31 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let result = match (first.number, increment.number, last.number) { (Number::Int(first), Number::Int(increment), last) => { let last = last.round_towards(&first); + let format = match options.format { + Some(f) => { + let f = Format::::parse(f)?; + Some(f) + } + None => None, + }; print_seq_integers( (first, increment, last), &options.separator, &options.terminator, options.equal_width, padding, - options.format, + format, ) } - (first, increment, last) => print_seq( + (first, increment, last) => { + let format = match options.format { + Some(f) => { + let f = Format::::parse(f)?; + Some(f) + } + None => None, + }; + print_seq( ( first.into_extended_big_decimal(), increment.into_extended_big_decimal(), @@ -139,8 +154,9 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { &options.terminator, options.equal_width, padding, - options.format, - ), + format, + ) + } }; match result { Ok(_) => Ok(()), @@ -244,7 +260,7 @@ fn print_seq( terminator: &str, pad: bool, padding: usize, - format: Option<&str>, + format: Option>, ) -> UResult<()> { let stdout = stdout(); let mut stdout = stdout.lock(); @@ -268,10 +284,16 @@ fn print_seq( // it as a string and ultimately writing to `stdout`. We // shouldn't have to do so much converting back and forth via // strings. - match format { + match &format { Some(f) => { - let s = format!("{value}"); - printf(f, &[FormatArgument::String(s)])?; + let float = match &value { + ExtendedBigDecimal::BigDecimal(bd) => bd.to_f64().unwrap(), + ExtendedBigDecimal::Infinity => f64::INFINITY, + ExtendedBigDecimal::MinusInfinity => f64::NEG_INFINITY, + ExtendedBigDecimal::MinusZero => -0.0, + ExtendedBigDecimal::Nan => f64::NAN, + }; + f.fmt(&mut stdout, float)?; } None => write_value_float(&mut stdout, &value, padding, largest_dec)?, } @@ -306,7 +328,7 @@ fn print_seq_integers( terminator: &str, pad: bool, padding: usize, - format: Option<&str>, + format: Option>, ) -> UResult<()> { let stdout = stdout(); let mut stdout = stdout.lock(); @@ -324,10 +346,16 @@ fn print_seq_integers( // the current value and writes the result to `stdout`. // // TODO See similar comment about formatting in `print_seq()`. - match format { + match &format { Some(f) => { - let s = format!("{value}"); - printf(f, &[FormatArgument::String(s)])?; + let int = match &value { + ExtendedBigInt::BigInt(bi) => bi.to_i64().unwrap(), + ExtendedBigInt::Infinity => todo!(), + ExtendedBigInt::MinusInfinity => todo!(), + ExtendedBigInt::MinusZero => todo!(), + ExtendedBigInt::Nan => todo!(), + }; + f.fmt(&mut stdout, int)?; } None => write_value_int(&mut stdout, &value, padding, pad)?, } diff --git a/src/uucore/src/lib/features/format/mod.rs b/src/uucore/src/lib/features/format/mod.rs index d6db5e8c7cd..48151be9892 100644 --- a/src/uucore/src/lib/features/format/mod.rs +++ b/src/uucore/src/lib/features/format/mod.rs @@ -10,8 +10,8 @@ //! parsing errors occur during writing. // spell-checker:ignore (vars) charf decf floatf intf scif strf Cninety -mod spec; pub mod num_format; +mod spec; use spec::Spec; use std::{ @@ -22,6 +22,8 @@ use std::{ use crate::error::UError; +use self::num_format::Formatter; + #[derive(Debug)] pub enum FormatError { SpecError, @@ -33,6 +35,12 @@ pub enum FormatError { impl Error for FormatError {} impl UError for FormatError {} +impl From for FormatError { + fn from(value: std::io::Error) -> Self { + FormatError::IoError(value) + } +} + impl Display for FormatError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { // TODO: Be more precise about these @@ -181,3 +189,65 @@ pub fn sprintf<'a>( printf_writer(&mut writer, format_string, arguments)?; Ok(writer) } + +/// A parsed format for a single float value +/// +/// This is used by `seq`. It can be constructed with [`FloatFormat::parse`] +/// and can write a value with [`FloatFormat::fmt`]. +/// +/// It can only accept a single specification without any asterisk parameters. +/// If it does get more specifications, it will return an error. +pub struct Format { + prefix: Vec, + suffix: Vec, + formatter: F, +} + +impl Format { + pub fn parse(format_string: impl AsRef<[u8]>) -> Result { + let mut iter = parse_iter(format_string.as_ref()); + + let mut prefix = Vec::new(); + let mut spec = None; + for item in &mut iter { + match item? { + FormatItem::Spec(s) => { + spec = Some(s); + break; + } + FormatItem::Text(t) => prefix.extend_from_slice(&t), + FormatItem::Char(c) => prefix.push(c), + } + } + + let Some(spec) = spec else { + return Err(FormatError::SpecError); + }; + + let formatter = F::try_from_spec(spec)?; + + let mut suffix = Vec::new(); + for item in &mut iter { + match item? { + FormatItem::Spec(_) => { + return Err(FormatError::SpecError); + } + FormatItem::Text(t) => suffix.extend_from_slice(&t), + FormatItem::Char(c) => suffix.push(c), + } + } + + Ok(Self { + prefix, + suffix, + formatter, + }) + } + + pub fn fmt(&self, mut w: impl Write, f: F::Input) -> std::io::Result<()> { + w.write_all(&self.prefix)?; + self.formatter.fmt(&mut w, f)?; + w.write_all(&self.suffix)?; + Ok(()) + } +} diff --git a/src/uucore/src/lib/features/format/num_format.rs b/src/uucore/src/lib/features/format/num_format.rs index 3a27ac200f8..fd010bdc029 100644 --- a/src/uucore/src/lib/features/format/num_format.rs +++ b/src/uucore/src/lib/features/format/num_format.rs @@ -1,10 +1,16 @@ use std::io::Write; -use super::FormatError; +use super::{ + spec::{CanAsterisk, Spec}, + FormatError, +}; pub trait Formatter { type Input; - fn fmt(&self, writer: impl Write, x: Self::Input) -> Result<(), FormatError>; + fn fmt(&self, writer: impl Write, x: Self::Input) -> std::io::Result<()>; + fn try_from_spec(s: Spec) -> Result + where + Self: Sized; } #[derive(Clone, Copy)] @@ -64,14 +70,13 @@ pub struct SignedInt { impl Formatter for SignedInt { type Input = i64; - fn fmt(&self, mut writer: impl Write, x: Self::Input) -> Result<(), FormatError> { + fn fmt(&self, mut writer: impl Write, x: Self::Input) -> std::io::Result<()> { if x >= 0 { match self.positive_sign { PositiveSign::None => Ok(()), PositiveSign::Plus => write!(writer, "+"), PositiveSign::Space => write!(writer, " "), - } - .map_err(FormatError::IoError)?; + }?; } match self.alignment { @@ -79,7 +84,29 @@ impl Formatter for SignedInt { NumberAlignment::RightSpace => write!(writer, "{x:>width$}", width = self.width), NumberAlignment::RightZero => write!(writer, "{x:0>width$}", width = self.width), } - .map_err(FormatError::IoError) + } + + fn try_from_spec(s: Spec) -> Result { + let Spec::SignedInt { + width, + positive_sign, + alignment, + } = s + else { + return Err(FormatError::SpecError); + }; + + let width = match width { + Some(CanAsterisk::Fixed(x)) => x, + None => 0, + Some(CanAsterisk::Asterisk) => return Err(FormatError::SpecError), + }; + + Ok(Self { + width, + positive_sign, + alignment, + }) } } @@ -92,7 +119,7 @@ pub struct UnsignedInt { impl Formatter for UnsignedInt { type Input = u64; - fn fmt(&self, mut writer: impl Write, x: Self::Input) -> Result<(), FormatError> { + fn fmt(&self, mut writer: impl Write, x: Self::Input) -> std::io::Result<()> { let s = match self.variant { UnsignedIntVariant::Decimal => format!("{x}"), UnsignedIntVariant::Octal(Prefix::No) => format!("{x:o}"), @@ -116,7 +143,29 @@ impl Formatter for UnsignedInt { NumberAlignment::RightSpace => write!(writer, "{s:>width$}", width = self.width), NumberAlignment::RightZero => write!(writer, "{s:0>width$}", width = self.width), } - .map_err(FormatError::IoError) + } + + fn try_from_spec(s: Spec) -> Result { + let Spec::UnsignedInt { + variant, + width, + alignment, + } = s + else { + return Err(FormatError::SpecError); + }; + + let width = match width { + Some(CanAsterisk::Fixed(x)) => x, + None => 0, + Some(CanAsterisk::Asterisk) => return Err(FormatError::SpecError), + }; + + Ok(Self { + width, + variant, + alignment, + }) } } @@ -147,14 +196,13 @@ impl Default for Float { impl Formatter for Float { type Input = f64; - fn fmt(&self, mut writer: impl Write, x: Self::Input) -> Result<(), FormatError> { + fn fmt(&self, mut writer: impl Write, x: Self::Input) -> std::io::Result<()> { if x.is_sign_positive() { match self.positive_sign { PositiveSign::None => Ok(()), PositiveSign::Plus => write!(writer, "+"), PositiveSign::Space => write!(writer, " "), - } - .map_err(FormatError::IoError)?; + }?; } let s = match self.variant { @@ -177,7 +225,46 @@ impl Formatter for Float { NumberAlignment::RightSpace => write!(writer, "{s:>width$}", width = self.width), NumberAlignment::RightZero => write!(writer, "{s:0>width$}", width = self.width), } - .map_err(FormatError::IoError) + } + + fn try_from_spec(s: Spec) -> Result + where + Self: Sized, + { + let Spec::Float { + variant, + case, + force_decimal, + width, + positive_sign, + alignment, + precision, + } = s + else { + return Err(FormatError::SpecError); + }; + + let width = match width { + Some(CanAsterisk::Fixed(x)) => x, + None => 0, + Some(CanAsterisk::Asterisk) => return Err(FormatError::SpecError), + }; + + let precision = match precision { + Some(CanAsterisk::Fixed(x)) => x, + None => 0, + Some(CanAsterisk::Asterisk) => return Err(FormatError::SpecError), + }; + + Ok(Self { + variant, + case, + force_decimal, + width, + positive_sign, + alignment, + precision, + }) } } diff --git a/src/uucore/src/lib/features/format/spec.rs b/src/uucore/src/lib/features/format/spec.rs index 80896997007..9c53669fa9c 100644 --- a/src/uucore/src/lib/features/format/spec.rs +++ b/src/uucore/src/lib/features/format/spec.rs @@ -252,6 +252,7 @@ impl Spec { alignment, } .fmt(writer, *i) + .map_err(FormatError::IoError) } &Spec::UnsignedInt { variant, @@ -271,6 +272,7 @@ impl Spec { alignment, } .fmt(writer, *i) + .map_err(FormatError::IoError) } &Spec::Float { variant, @@ -299,6 +301,7 @@ impl Spec { precision, } .fmt(writer, *f) + .map_err(FormatError::IoError) } } } From 1f40cd69c80380192cde49bdfc6c4437f4a0e061 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 13 Nov 2023 18:55:11 +0000 Subject: [PATCH 0343/2851] chore(deps): update actions/github-script action to v7 --- .github/workflows/GnuComment.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/GnuComment.yml b/.github/workflows/GnuComment.yml index bb64232a99a..36c54490ce9 100644 --- a/.github/workflows/GnuComment.yml +++ b/.github/workflows/GnuComment.yml @@ -18,7 +18,7 @@ jobs: github.event.workflow_run.event == 'pull_request' steps: - name: 'Download artifact' - uses: actions/github-script@v6 + uses: actions/github-script@v7 with: script: | // List all artifacts from GnuTests @@ -43,7 +43,7 @@ jobs: - run: unzip comment.zip - name: 'Comment on PR' - uses: actions/github-script@v6 + uses: actions/github-script@v7 with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | From ff92bfb25be0e996f25b82e3d9e78884e573e655 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Mon, 13 Nov 2023 21:50:59 +0100 Subject: [PATCH 0344/2851] Prepare version 0.0.23 --- Cargo.lock | 214 ++++++++++++------------- Cargo.toml | 206 ++++++++++++------------ src/uu/arch/Cargo.toml | 2 +- src/uu/base32/Cargo.toml | 2 +- src/uu/base64/Cargo.toml | 2 +- src/uu/basename/Cargo.toml | 2 +- src/uu/basenc/Cargo.toml | 2 +- src/uu/cat/Cargo.toml | 2 +- src/uu/chcon/Cargo.toml | 2 +- src/uu/chgrp/Cargo.toml | 2 +- src/uu/chmod/Cargo.toml | 2 +- src/uu/chown/Cargo.toml | 2 +- src/uu/chroot/Cargo.toml | 2 +- src/uu/cksum/Cargo.toml | 2 +- src/uu/comm/Cargo.toml | 2 +- src/uu/cp/Cargo.toml | 2 +- src/uu/csplit/Cargo.toml | 2 +- src/uu/cut/Cargo.toml | 2 +- src/uu/date/Cargo.toml | 2 +- src/uu/dd/Cargo.toml | 2 +- src/uu/df/Cargo.toml | 2 +- src/uu/dir/Cargo.toml | 2 +- src/uu/dircolors/Cargo.toml | 2 +- src/uu/dirname/Cargo.toml | 2 +- src/uu/du/Cargo.toml | 2 +- src/uu/echo/Cargo.toml | 2 +- src/uu/env/Cargo.toml | 2 +- src/uu/expand/Cargo.toml | 2 +- src/uu/expr/Cargo.toml | 2 +- src/uu/factor/Cargo.toml | 2 +- src/uu/false/Cargo.toml | 2 +- src/uu/fmt/Cargo.toml | 2 +- src/uu/fold/Cargo.toml | 2 +- src/uu/groups/Cargo.toml | 2 +- src/uu/hashsum/Cargo.toml | 2 +- src/uu/head/Cargo.toml | 2 +- src/uu/hostid/Cargo.toml | 2 +- src/uu/hostname/Cargo.toml | 2 +- src/uu/id/Cargo.toml | 2 +- src/uu/install/Cargo.toml | 2 +- src/uu/join/Cargo.toml | 2 +- src/uu/kill/Cargo.toml | 2 +- src/uu/link/Cargo.toml | 2 +- src/uu/ln/Cargo.toml | 2 +- src/uu/logname/Cargo.toml | 2 +- src/uu/ls/Cargo.toml | 2 +- src/uu/mkdir/Cargo.toml | 2 +- src/uu/mkfifo/Cargo.toml | 2 +- src/uu/mknod/Cargo.toml | 2 +- src/uu/mktemp/Cargo.toml | 2 +- src/uu/more/Cargo.toml | 2 +- src/uu/mv/Cargo.toml | 2 +- src/uu/nice/Cargo.toml | 2 +- src/uu/nl/Cargo.toml | 2 +- src/uu/nohup/Cargo.toml | 2 +- src/uu/nproc/Cargo.toml | 2 +- src/uu/numfmt/Cargo.toml | 2 +- src/uu/od/Cargo.toml | 2 +- src/uu/paste/Cargo.toml | 2 +- src/uu/pathchk/Cargo.toml | 2 +- src/uu/pinky/Cargo.toml | 2 +- src/uu/pr/Cargo.toml | 2 +- src/uu/printenv/Cargo.toml | 2 +- src/uu/printf/Cargo.toml | 2 +- src/uu/ptx/Cargo.toml | 2 +- src/uu/pwd/Cargo.toml | 2 +- src/uu/readlink/Cargo.toml | 2 +- src/uu/realpath/Cargo.toml | 2 +- src/uu/rm/Cargo.toml | 2 +- src/uu/rmdir/Cargo.toml | 2 +- src/uu/runcon/Cargo.toml | 2 +- src/uu/seq/Cargo.toml | 2 +- src/uu/shred/Cargo.toml | 2 +- src/uu/shuf/Cargo.toml | 2 +- src/uu/sleep/Cargo.toml | 2 +- src/uu/sort/Cargo.toml | 2 +- src/uu/split/Cargo.toml | 2 +- src/uu/stat/Cargo.toml | 2 +- src/uu/stdbuf/Cargo.toml | 4 +- src/uu/stdbuf/src/libstdbuf/Cargo.toml | 2 +- src/uu/stty/Cargo.toml | 2 +- src/uu/sum/Cargo.toml | 2 +- src/uu/sync/Cargo.toml | 2 +- src/uu/tac/Cargo.toml | 2 +- src/uu/tail/Cargo.toml | 2 +- src/uu/tee/Cargo.toml | 2 +- src/uu/test/Cargo.toml | 2 +- src/uu/timeout/Cargo.toml | 2 +- src/uu/touch/Cargo.toml | 2 +- src/uu/tr/Cargo.toml | 2 +- src/uu/true/Cargo.toml | 2 +- src/uu/truncate/Cargo.toml | 2 +- src/uu/tsort/Cargo.toml | 2 +- src/uu/tty/Cargo.toml | 2 +- src/uu/uname/Cargo.toml | 2 +- src/uu/unexpand/Cargo.toml | 2 +- src/uu/uniq/Cargo.toml | 2 +- src/uu/unlink/Cargo.toml | 2 +- src/uu/uptime/Cargo.toml | 2 +- src/uu/users/Cargo.toml | 2 +- src/uu/vdir/Cargo.toml | 2 +- src/uu/wc/Cargo.toml | 2 +- src/uu/who/Cargo.toml | 2 +- src/uu/whoami/Cargo.toml | 2 +- src/uu/yes/Cargo.toml | 2 +- src/uucore/Cargo.toml | 2 +- src/uucore_procs/Cargo.toml | 4 +- src/uuhelp_parser/Cargo.toml | 2 +- util/update-version.sh | 4 +- 109 files changed, 320 insertions(+), 320 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2496bbb7b79..d83adfee600 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -374,7 +374,7 @@ checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" [[package]] name = "coreutils" -version = "0.0.22" +version = "0.0.23" dependencies = [ "chrono", "clap", @@ -2207,7 +2207,7 @@ checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" [[package]] name = "uu_arch" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "platform-info", @@ -2216,7 +2216,7 @@ dependencies = [ [[package]] name = "uu_base32" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "uucore", @@ -2224,7 +2224,7 @@ dependencies = [ [[package]] name = "uu_base64" -version = "0.0.22" +version = "0.0.23" dependencies = [ "uu_base32", "uucore", @@ -2232,7 +2232,7 @@ dependencies = [ [[package]] name = "uu_basename" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "uucore", @@ -2240,7 +2240,7 @@ dependencies = [ [[package]] name = "uu_basenc" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "uu_base32", @@ -2249,7 +2249,7 @@ dependencies = [ [[package]] name = "uu_cat" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "nix", @@ -2259,7 +2259,7 @@ dependencies = [ [[package]] name = "uu_chcon" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "fts-sys", @@ -2271,7 +2271,7 @@ dependencies = [ [[package]] name = "uu_chgrp" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "uucore", @@ -2279,7 +2279,7 @@ dependencies = [ [[package]] name = "uu_chmod" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "libc", @@ -2288,7 +2288,7 @@ dependencies = [ [[package]] name = "uu_chown" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "uucore", @@ -2296,7 +2296,7 @@ dependencies = [ [[package]] name = "uu_chroot" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "uucore", @@ -2304,7 +2304,7 @@ dependencies = [ [[package]] name = "uu_cksum" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "hex", @@ -2313,7 +2313,7 @@ dependencies = [ [[package]] name = "uu_comm" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "uucore", @@ -2321,7 +2321,7 @@ dependencies = [ [[package]] name = "uu_cp" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "exacl", @@ -2337,7 +2337,7 @@ dependencies = [ [[package]] name = "uu_csplit" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "regex", @@ -2347,7 +2347,7 @@ dependencies = [ [[package]] name = "uu_cut" -version = "0.0.22" +version = "0.0.23" dependencies = [ "bstr", "clap", @@ -2357,7 +2357,7 @@ dependencies = [ [[package]] name = "uu_date" -version = "0.0.22" +version = "0.0.23" dependencies = [ "chrono", "clap", @@ -2369,7 +2369,7 @@ dependencies = [ [[package]] name = "uu_dd" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "gcd", @@ -2381,7 +2381,7 @@ dependencies = [ [[package]] name = "uu_df" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "tempfile", @@ -2391,7 +2391,7 @@ dependencies = [ [[package]] name = "uu_dir" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "uu_ls", @@ -2400,7 +2400,7 @@ dependencies = [ [[package]] name = "uu_dircolors" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "uucore", @@ -2408,7 +2408,7 @@ dependencies = [ [[package]] name = "uu_dirname" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "uucore", @@ -2416,7 +2416,7 @@ dependencies = [ [[package]] name = "uu_du" -version = "0.0.22" +version = "0.0.23" dependencies = [ "chrono", "clap", @@ -2427,7 +2427,7 @@ dependencies = [ [[package]] name = "uu_echo" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "uucore", @@ -2435,7 +2435,7 @@ dependencies = [ [[package]] name = "uu_env" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "nix", @@ -2445,7 +2445,7 @@ dependencies = [ [[package]] name = "uu_expand" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "unicode-width", @@ -2454,7 +2454,7 @@ dependencies = [ [[package]] name = "uu_expr" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "num-bigint", @@ -2465,7 +2465,7 @@ dependencies = [ [[package]] name = "uu_factor" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "coz", @@ -2478,7 +2478,7 @@ dependencies = [ [[package]] name = "uu_false" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "uucore", @@ -2486,7 +2486,7 @@ dependencies = [ [[package]] name = "uu_fmt" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "unicode-width", @@ -2495,7 +2495,7 @@ dependencies = [ [[package]] name = "uu_fold" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "uucore", @@ -2503,7 +2503,7 @@ dependencies = [ [[package]] name = "uu_groups" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "uucore", @@ -2511,7 +2511,7 @@ dependencies = [ [[package]] name = "uu_hashsum" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "hex", @@ -2522,7 +2522,7 @@ dependencies = [ [[package]] name = "uu_head" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "memchr", @@ -2531,7 +2531,7 @@ dependencies = [ [[package]] name = "uu_hostid" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "libc", @@ -2540,7 +2540,7 @@ dependencies = [ [[package]] name = "uu_hostname" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "hostname", @@ -2550,7 +2550,7 @@ dependencies = [ [[package]] name = "uu_id" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "selinux", @@ -2559,7 +2559,7 @@ dependencies = [ [[package]] name = "uu_install" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "file_diff", @@ -2570,7 +2570,7 @@ dependencies = [ [[package]] name = "uu_join" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "memchr", @@ -2579,7 +2579,7 @@ dependencies = [ [[package]] name = "uu_kill" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "nix", @@ -2588,7 +2588,7 @@ dependencies = [ [[package]] name = "uu_link" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "uucore", @@ -2596,7 +2596,7 @@ dependencies = [ [[package]] name = "uu_ln" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "uucore", @@ -2604,7 +2604,7 @@ dependencies = [ [[package]] name = "uu_logname" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "libc", @@ -2613,7 +2613,7 @@ dependencies = [ [[package]] name = "uu_ls" -version = "0.0.22" +version = "0.0.23" dependencies = [ "chrono", "clap", @@ -2630,7 +2630,7 @@ dependencies = [ [[package]] name = "uu_mkdir" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "uucore", @@ -2638,7 +2638,7 @@ dependencies = [ [[package]] name = "uu_mkfifo" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "libc", @@ -2647,7 +2647,7 @@ dependencies = [ [[package]] name = "uu_mknod" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "libc", @@ -2656,7 +2656,7 @@ dependencies = [ [[package]] name = "uu_mktemp" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "rand", @@ -2666,7 +2666,7 @@ dependencies = [ [[package]] name = "uu_more" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "crossterm", @@ -2678,7 +2678,7 @@ dependencies = [ [[package]] name = "uu_mv" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "fs_extra", @@ -2688,7 +2688,7 @@ dependencies = [ [[package]] name = "uu_nice" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "libc", @@ -2698,7 +2698,7 @@ dependencies = [ [[package]] name = "uu_nl" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "regex", @@ -2707,7 +2707,7 @@ dependencies = [ [[package]] name = "uu_nohup" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "libc", @@ -2716,7 +2716,7 @@ dependencies = [ [[package]] name = "uu_nproc" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "libc", @@ -2725,7 +2725,7 @@ dependencies = [ [[package]] name = "uu_numfmt" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "uucore", @@ -2733,7 +2733,7 @@ dependencies = [ [[package]] name = "uu_od" -version = "0.0.22" +version = "0.0.23" dependencies = [ "byteorder", "clap", @@ -2743,7 +2743,7 @@ dependencies = [ [[package]] name = "uu_paste" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "uucore", @@ -2751,7 +2751,7 @@ dependencies = [ [[package]] name = "uu_pathchk" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "libc", @@ -2760,7 +2760,7 @@ dependencies = [ [[package]] name = "uu_pinky" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "uucore", @@ -2768,7 +2768,7 @@ dependencies = [ [[package]] name = "uu_pr" -version = "0.0.22" +version = "0.0.23" dependencies = [ "chrono", "clap", @@ -2780,7 +2780,7 @@ dependencies = [ [[package]] name = "uu_printenv" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "uucore", @@ -2788,7 +2788,7 @@ dependencies = [ [[package]] name = "uu_printf" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "uucore", @@ -2796,7 +2796,7 @@ dependencies = [ [[package]] name = "uu_ptx" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "regex", @@ -2805,7 +2805,7 @@ dependencies = [ [[package]] name = "uu_pwd" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "uucore", @@ -2813,7 +2813,7 @@ dependencies = [ [[package]] name = "uu_readlink" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "uucore", @@ -2821,7 +2821,7 @@ dependencies = [ [[package]] name = "uu_realpath" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "uucore", @@ -2829,7 +2829,7 @@ dependencies = [ [[package]] name = "uu_rm" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "libc", @@ -2840,7 +2840,7 @@ dependencies = [ [[package]] name = "uu_rmdir" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "libc", @@ -2849,7 +2849,7 @@ dependencies = [ [[package]] name = "uu_runcon" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "libc", @@ -2860,7 +2860,7 @@ dependencies = [ [[package]] name = "uu_seq" -version = "0.0.22" +version = "0.0.23" dependencies = [ "bigdecimal", "clap", @@ -2871,7 +2871,7 @@ dependencies = [ [[package]] name = "uu_shred" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "libc", @@ -2881,7 +2881,7 @@ dependencies = [ [[package]] name = "uu_shuf" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "memchr", @@ -2892,7 +2892,7 @@ dependencies = [ [[package]] name = "uu_sleep" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "fundu", @@ -2901,7 +2901,7 @@ dependencies = [ [[package]] name = "uu_sort" -version = "0.0.22" +version = "0.0.23" dependencies = [ "binary-heap-plus", "clap", @@ -2920,7 +2920,7 @@ dependencies = [ [[package]] name = "uu_split" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "memchr", @@ -2929,7 +2929,7 @@ dependencies = [ [[package]] name = "uu_stat" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "uucore", @@ -2937,7 +2937,7 @@ dependencies = [ [[package]] name = "uu_stdbuf" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "tempfile", @@ -2947,7 +2947,7 @@ dependencies = [ [[package]] name = "uu_stdbuf_libstdbuf" -version = "0.0.22" +version = "0.0.23" dependencies = [ "cpp", "cpp_build", @@ -2957,7 +2957,7 @@ dependencies = [ [[package]] name = "uu_stty" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "nix", @@ -2966,7 +2966,7 @@ dependencies = [ [[package]] name = "uu_sum" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "uucore", @@ -2974,7 +2974,7 @@ dependencies = [ [[package]] name = "uu_sync" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "libc", @@ -2985,7 +2985,7 @@ dependencies = [ [[package]] name = "uu_tac" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "memchr", @@ -2996,7 +2996,7 @@ dependencies = [ [[package]] name = "uu_tail" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "fundu", @@ -3012,7 +3012,7 @@ dependencies = [ [[package]] name = "uu_tee" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "libc", @@ -3021,7 +3021,7 @@ dependencies = [ [[package]] name = "uu_test" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "libc", @@ -3031,7 +3031,7 @@ dependencies = [ [[package]] name = "uu_timeout" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "libc", @@ -3041,7 +3041,7 @@ dependencies = [ [[package]] name = "uu_touch" -version = "0.0.22" +version = "0.0.23" dependencies = [ "chrono", "clap", @@ -3053,7 +3053,7 @@ dependencies = [ [[package]] name = "uu_tr" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "nom", @@ -3062,7 +3062,7 @@ dependencies = [ [[package]] name = "uu_true" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "uucore", @@ -3070,7 +3070,7 @@ dependencies = [ [[package]] name = "uu_truncate" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "uucore", @@ -3078,7 +3078,7 @@ dependencies = [ [[package]] name = "uu_tsort" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "uucore", @@ -3086,7 +3086,7 @@ dependencies = [ [[package]] name = "uu_tty" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "nix", @@ -3095,7 +3095,7 @@ dependencies = [ [[package]] name = "uu_uname" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "platform-info", @@ -3104,7 +3104,7 @@ dependencies = [ [[package]] name = "uu_unexpand" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "unicode-width", @@ -3113,7 +3113,7 @@ dependencies = [ [[package]] name = "uu_uniq" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "uucore", @@ -3121,7 +3121,7 @@ dependencies = [ [[package]] name = "uu_unlink" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "uucore", @@ -3129,7 +3129,7 @@ dependencies = [ [[package]] name = "uu_uptime" -version = "0.0.22" +version = "0.0.23" dependencies = [ "chrono", "clap", @@ -3138,7 +3138,7 @@ dependencies = [ [[package]] name = "uu_users" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "uucore", @@ -3146,7 +3146,7 @@ dependencies = [ [[package]] name = "uu_vdir" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "uu_ls", @@ -3155,7 +3155,7 @@ dependencies = [ [[package]] name = "uu_wc" -version = "0.0.22" +version = "0.0.23" dependencies = [ "bytecount", "clap", @@ -3168,7 +3168,7 @@ dependencies = [ [[package]] name = "uu_who" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "uucore", @@ -3176,7 +3176,7 @@ dependencies = [ [[package]] name = "uu_whoami" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "libc", @@ -3186,7 +3186,7 @@ dependencies = [ [[package]] name = "uu_yes" -version = "0.0.22" +version = "0.0.23" dependencies = [ "clap", "itertools", @@ -3196,7 +3196,7 @@ dependencies = [ [[package]] name = "uucore" -version = "0.0.22" +version = "0.0.23" dependencies = [ "blake2b_simd", "blake3", @@ -3232,7 +3232,7 @@ dependencies = [ [[package]] name = "uucore_procs" -version = "0.0.22" +version = "0.0.23" dependencies = [ "proc-macro2", "quote", @@ -3241,7 +3241,7 @@ dependencies = [ [[package]] name = "uuhelp_parser" -version = "0.0.22" +version = "0.0.23" [[package]] name = "uuid" diff --git a/Cargo.toml b/Cargo.toml index b4a0dc07c26..f313e2b03e4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ [package] name = "coreutils" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "coreutils ~ GNU coreutils (updated); implemented as universal (cross-platform) utils, written in Rust" @@ -361,109 +361,109 @@ zip = { workspace = true, optional = true } uuhelp_parser = { optional = true, version = ">=0.0.19", path = "src/uuhelp_parser" } # * uutils -uu_test = { optional = true, version = "0.0.22", package = "uu_test", path = "src/uu/test" } +uu_test = { optional = true, version = "0.0.23", package = "uu_test", path = "src/uu/test" } # -arch = { optional = true, version = "0.0.22", package = "uu_arch", path = "src/uu/arch" } -base32 = { optional = true, version = "0.0.22", package = "uu_base32", path = "src/uu/base32" } -base64 = { optional = true, version = "0.0.22", package = "uu_base64", path = "src/uu/base64" } -basename = { optional = true, version = "0.0.22", package = "uu_basename", path = "src/uu/basename" } -basenc = { optional = true, version = "0.0.22", package = "uu_basenc", path = "src/uu/basenc" } -cat = { optional = true, version = "0.0.22", package = "uu_cat", path = "src/uu/cat" } -chcon = { optional = true, version = "0.0.22", package = "uu_chcon", path = "src/uu/chcon" } -chgrp = { optional = true, version = "0.0.22", package = "uu_chgrp", path = "src/uu/chgrp" } -chmod = { optional = true, version = "0.0.22", package = "uu_chmod", path = "src/uu/chmod" } -chown = { optional = true, version = "0.0.22", package = "uu_chown", path = "src/uu/chown" } -chroot = { optional = true, version = "0.0.22", package = "uu_chroot", path = "src/uu/chroot" } -cksum = { optional = true, version = "0.0.22", package = "uu_cksum", path = "src/uu/cksum" } -comm = { optional = true, version = "0.0.22", package = "uu_comm", path = "src/uu/comm" } -cp = { optional = true, version = "0.0.22", package = "uu_cp", path = "src/uu/cp" } -csplit = { optional = true, version = "0.0.22", package = "uu_csplit", path = "src/uu/csplit" } -cut = { optional = true, version = "0.0.22", package = "uu_cut", path = "src/uu/cut" } -date = { optional = true, version = "0.0.22", package = "uu_date", path = "src/uu/date" } -dd = { optional = true, version = "0.0.22", package = "uu_dd", path = "src/uu/dd" } -df = { optional = true, version = "0.0.22", package = "uu_df", path = "src/uu/df" } -dir = { optional = true, version = "0.0.22", package = "uu_dir", path = "src/uu/dir" } -dircolors = { optional = true, version = "0.0.22", package = "uu_dircolors", path = "src/uu/dircolors" } -dirname = { optional = true, version = "0.0.22", package = "uu_dirname", path = "src/uu/dirname" } -du = { optional = true, version = "0.0.22", package = "uu_du", path = "src/uu/du" } -echo = { optional = true, version = "0.0.22", package = "uu_echo", path = "src/uu/echo" } -env = { optional = true, version = "0.0.22", package = "uu_env", path = "src/uu/env" } -expand = { optional = true, version = "0.0.22", package = "uu_expand", path = "src/uu/expand" } -expr = { optional = true, version = "0.0.22", package = "uu_expr", path = "src/uu/expr" } -factor = { optional = true, version = "0.0.22", package = "uu_factor", path = "src/uu/factor" } -false = { optional = true, version = "0.0.22", package = "uu_false", path = "src/uu/false" } -fmt = { optional = true, version = "0.0.22", package = "uu_fmt", path = "src/uu/fmt" } -fold = { optional = true, version = "0.0.22", package = "uu_fold", path = "src/uu/fold" } -groups = { optional = true, version = "0.0.22", package = "uu_groups", path = "src/uu/groups" } -hashsum = { optional = true, version = "0.0.22", package = "uu_hashsum", path = "src/uu/hashsum" } -head = { optional = true, version = "0.0.22", package = "uu_head", path = "src/uu/head" } -hostid = { optional = true, version = "0.0.22", package = "uu_hostid", path = "src/uu/hostid" } -hostname = { optional = true, version = "0.0.22", package = "uu_hostname", path = "src/uu/hostname" } -id = { optional = true, version = "0.0.22", package = "uu_id", path = "src/uu/id" } -install = { optional = true, version = "0.0.22", package = "uu_install", path = "src/uu/install" } -join = { optional = true, version = "0.0.22", package = "uu_join", path = "src/uu/join" } -kill = { optional = true, version = "0.0.22", package = "uu_kill", path = "src/uu/kill" } -link = { optional = true, version = "0.0.22", package = "uu_link", path = "src/uu/link" } -ln = { optional = true, version = "0.0.22", package = "uu_ln", path = "src/uu/ln" } -ls = { optional = true, version = "0.0.22", package = "uu_ls", path = "src/uu/ls" } -logname = { optional = true, version = "0.0.22", package = "uu_logname", path = "src/uu/logname" } -mkdir = { optional = true, version = "0.0.22", package = "uu_mkdir", path = "src/uu/mkdir" } -mkfifo = { optional = true, version = "0.0.22", package = "uu_mkfifo", path = "src/uu/mkfifo" } -mknod = { optional = true, version = "0.0.22", package = "uu_mknod", path = "src/uu/mknod" } -mktemp = { optional = true, version = "0.0.22", package = "uu_mktemp", path = "src/uu/mktemp" } -more = { optional = true, version = "0.0.22", package = "uu_more", path = "src/uu/more" } -mv = { optional = true, version = "0.0.22", package = "uu_mv", path = "src/uu/mv" } -nice = { optional = true, version = "0.0.22", package = "uu_nice", path = "src/uu/nice" } -nl = { optional = true, version = "0.0.22", package = "uu_nl", path = "src/uu/nl" } -nohup = { optional = true, version = "0.0.22", package = "uu_nohup", path = "src/uu/nohup" } -nproc = { optional = true, version = "0.0.22", package = "uu_nproc", path = "src/uu/nproc" } -numfmt = { optional = true, version = "0.0.22", package = "uu_numfmt", path = "src/uu/numfmt" } -od = { optional = true, version = "0.0.22", package = "uu_od", path = "src/uu/od" } -paste = { optional = true, version = "0.0.22", package = "uu_paste", path = "src/uu/paste" } -pathchk = { optional = true, version = "0.0.22", package = "uu_pathchk", path = "src/uu/pathchk" } -pinky = { optional = true, version = "0.0.22", package = "uu_pinky", path = "src/uu/pinky" } -pr = { optional = true, version = "0.0.22", package = "uu_pr", path = "src/uu/pr" } -printenv = { optional = true, version = "0.0.22", package = "uu_printenv", path = "src/uu/printenv" } -printf = { optional = true, version = "0.0.22", package = "uu_printf", path = "src/uu/printf" } -ptx = { optional = true, version = "0.0.22", package = "uu_ptx", path = "src/uu/ptx" } -pwd = { optional = true, version = "0.0.22", package = "uu_pwd", path = "src/uu/pwd" } -readlink = { optional = true, version = "0.0.22", package = "uu_readlink", path = "src/uu/readlink" } -realpath = { optional = true, version = "0.0.22", package = "uu_realpath", path = "src/uu/realpath" } -rm = { optional = true, version = "0.0.22", package = "uu_rm", path = "src/uu/rm" } -rmdir = { optional = true, version = "0.0.22", package = "uu_rmdir", path = "src/uu/rmdir" } -runcon = { optional = true, version = "0.0.22", package = "uu_runcon", path = "src/uu/runcon" } -seq = { optional = true, version = "0.0.22", package = "uu_seq", path = "src/uu/seq" } -shred = { optional = true, version = "0.0.22", package = "uu_shred", path = "src/uu/shred" } -shuf = { optional = true, version = "0.0.22", package = "uu_shuf", path = "src/uu/shuf" } -sleep = { optional = true, version = "0.0.22", package = "uu_sleep", path = "src/uu/sleep" } -sort = { optional = true, version = "0.0.22", package = "uu_sort", path = "src/uu/sort" } -split = { optional = true, version = "0.0.22", package = "uu_split", path = "src/uu/split" } -stat = { optional = true, version = "0.0.22", package = "uu_stat", path = "src/uu/stat" } -stdbuf = { optional = true, version = "0.0.22", package = "uu_stdbuf", path = "src/uu/stdbuf" } -stty = { optional = true, version = "0.0.22", package = "uu_stty", path = "src/uu/stty" } -sum = { optional = true, version = "0.0.22", package = "uu_sum", path = "src/uu/sum" } -sync = { optional = true, version = "0.0.22", package = "uu_sync", path = "src/uu/sync" } -tac = { optional = true, version = "0.0.22", package = "uu_tac", path = "src/uu/tac" } -tail = { optional = true, version = "0.0.22", package = "uu_tail", path = "src/uu/tail" } -tee = { optional = true, version = "0.0.22", package = "uu_tee", path = "src/uu/tee" } -timeout = { optional = true, version = "0.0.22", package = "uu_timeout", path = "src/uu/timeout" } -touch = { optional = true, version = "0.0.22", package = "uu_touch", path = "src/uu/touch" } -tr = { optional = true, version = "0.0.22", package = "uu_tr", path = "src/uu/tr" } -true = { optional = true, version = "0.0.22", package = "uu_true", path = "src/uu/true" } -truncate = { optional = true, version = "0.0.22", package = "uu_truncate", path = "src/uu/truncate" } -tsort = { optional = true, version = "0.0.22", package = "uu_tsort", path = "src/uu/tsort" } -tty = { optional = true, version = "0.0.22", package = "uu_tty", path = "src/uu/tty" } -uname = { optional = true, version = "0.0.22", package = "uu_uname", path = "src/uu/uname" } -unexpand = { optional = true, version = "0.0.22", package = "uu_unexpand", path = "src/uu/unexpand" } -uniq = { optional = true, version = "0.0.22", package = "uu_uniq", path = "src/uu/uniq" } -unlink = { optional = true, version = "0.0.22", package = "uu_unlink", path = "src/uu/unlink" } -uptime = { optional = true, version = "0.0.22", package = "uu_uptime", path = "src/uu/uptime" } -users = { optional = true, version = "0.0.22", package = "uu_users", path = "src/uu/users" } -vdir = { optional = true, version = "0.0.22", package = "uu_vdir", path = "src/uu/vdir" } -wc = { optional = true, version = "0.0.22", package = "uu_wc", path = "src/uu/wc" } -who = { optional = true, version = "0.0.22", package = "uu_who", path = "src/uu/who" } -whoami = { optional = true, version = "0.0.22", package = "uu_whoami", path = "src/uu/whoami" } -yes = { optional = true, version = "0.0.22", package = "uu_yes", path = "src/uu/yes" } +arch = { optional = true, version = "0.0.23", package = "uu_arch", path = "src/uu/arch" } +base32 = { optional = true, version = "0.0.23", package = "uu_base32", path = "src/uu/base32" } +base64 = { optional = true, version = "0.0.23", package = "uu_base64", path = "src/uu/base64" } +basename = { optional = true, version = "0.0.23", package = "uu_basename", path = "src/uu/basename" } +basenc = { optional = true, version = "0.0.23", package = "uu_basenc", path = "src/uu/basenc" } +cat = { optional = true, version = "0.0.23", package = "uu_cat", path = "src/uu/cat" } +chcon = { optional = true, version = "0.0.23", package = "uu_chcon", path = "src/uu/chcon" } +chgrp = { optional = true, version = "0.0.23", package = "uu_chgrp", path = "src/uu/chgrp" } +chmod = { optional = true, version = "0.0.23", package = "uu_chmod", path = "src/uu/chmod" } +chown = { optional = true, version = "0.0.23", package = "uu_chown", path = "src/uu/chown" } +chroot = { optional = true, version = "0.0.23", package = "uu_chroot", path = "src/uu/chroot" } +cksum = { optional = true, version = "0.0.23", package = "uu_cksum", path = "src/uu/cksum" } +comm = { optional = true, version = "0.0.23", package = "uu_comm", path = "src/uu/comm" } +cp = { optional = true, version = "0.0.23", package = "uu_cp", path = "src/uu/cp" } +csplit = { optional = true, version = "0.0.23", package = "uu_csplit", path = "src/uu/csplit" } +cut = { optional = true, version = "0.0.23", package = "uu_cut", path = "src/uu/cut" } +date = { optional = true, version = "0.0.23", package = "uu_date", path = "src/uu/date" } +dd = { optional = true, version = "0.0.23", package = "uu_dd", path = "src/uu/dd" } +df = { optional = true, version = "0.0.23", package = "uu_df", path = "src/uu/df" } +dir = { optional = true, version = "0.0.23", package = "uu_dir", path = "src/uu/dir" } +dircolors = { optional = true, version = "0.0.23", package = "uu_dircolors", path = "src/uu/dircolors" } +dirname = { optional = true, version = "0.0.23", package = "uu_dirname", path = "src/uu/dirname" } +du = { optional = true, version = "0.0.23", package = "uu_du", path = "src/uu/du" } +echo = { optional = true, version = "0.0.23", package = "uu_echo", path = "src/uu/echo" } +env = { optional = true, version = "0.0.23", package = "uu_env", path = "src/uu/env" } +expand = { optional = true, version = "0.0.23", package = "uu_expand", path = "src/uu/expand" } +expr = { optional = true, version = "0.0.23", package = "uu_expr", path = "src/uu/expr" } +factor = { optional = true, version = "0.0.23", package = "uu_factor", path = "src/uu/factor" } +false = { optional = true, version = "0.0.23", package = "uu_false", path = "src/uu/false" } +fmt = { optional = true, version = "0.0.23", package = "uu_fmt", path = "src/uu/fmt" } +fold = { optional = true, version = "0.0.23", package = "uu_fold", path = "src/uu/fold" } +groups = { optional = true, version = "0.0.23", package = "uu_groups", path = "src/uu/groups" } +hashsum = { optional = true, version = "0.0.23", package = "uu_hashsum", path = "src/uu/hashsum" } +head = { optional = true, version = "0.0.23", package = "uu_head", path = "src/uu/head" } +hostid = { optional = true, version = "0.0.23", package = "uu_hostid", path = "src/uu/hostid" } +hostname = { optional = true, version = "0.0.23", package = "uu_hostname", path = "src/uu/hostname" } +id = { optional = true, version = "0.0.23", package = "uu_id", path = "src/uu/id" } +install = { optional = true, version = "0.0.23", package = "uu_install", path = "src/uu/install" } +join = { optional = true, version = "0.0.23", package = "uu_join", path = "src/uu/join" } +kill = { optional = true, version = "0.0.23", package = "uu_kill", path = "src/uu/kill" } +link = { optional = true, version = "0.0.23", package = "uu_link", path = "src/uu/link" } +ln = { optional = true, version = "0.0.23", package = "uu_ln", path = "src/uu/ln" } +ls = { optional = true, version = "0.0.23", package = "uu_ls", path = "src/uu/ls" } +logname = { optional = true, version = "0.0.23", package = "uu_logname", path = "src/uu/logname" } +mkdir = { optional = true, version = "0.0.23", package = "uu_mkdir", path = "src/uu/mkdir" } +mkfifo = { optional = true, version = "0.0.23", package = "uu_mkfifo", path = "src/uu/mkfifo" } +mknod = { optional = true, version = "0.0.23", package = "uu_mknod", path = "src/uu/mknod" } +mktemp = { optional = true, version = "0.0.23", package = "uu_mktemp", path = "src/uu/mktemp" } +more = { optional = true, version = "0.0.23", package = "uu_more", path = "src/uu/more" } +mv = { optional = true, version = "0.0.23", package = "uu_mv", path = "src/uu/mv" } +nice = { optional = true, version = "0.0.23", package = "uu_nice", path = "src/uu/nice" } +nl = { optional = true, version = "0.0.23", package = "uu_nl", path = "src/uu/nl" } +nohup = { optional = true, version = "0.0.23", package = "uu_nohup", path = "src/uu/nohup" } +nproc = { optional = true, version = "0.0.23", package = "uu_nproc", path = "src/uu/nproc" } +numfmt = { optional = true, version = "0.0.23", package = "uu_numfmt", path = "src/uu/numfmt" } +od = { optional = true, version = "0.0.23", package = "uu_od", path = "src/uu/od" } +paste = { optional = true, version = "0.0.23", package = "uu_paste", path = "src/uu/paste" } +pathchk = { optional = true, version = "0.0.23", package = "uu_pathchk", path = "src/uu/pathchk" } +pinky = { optional = true, version = "0.0.23", package = "uu_pinky", path = "src/uu/pinky" } +pr = { optional = true, version = "0.0.23", package = "uu_pr", path = "src/uu/pr" } +printenv = { optional = true, version = "0.0.23", package = "uu_printenv", path = "src/uu/printenv" } +printf = { optional = true, version = "0.0.23", package = "uu_printf", path = "src/uu/printf" } +ptx = { optional = true, version = "0.0.23", package = "uu_ptx", path = "src/uu/ptx" } +pwd = { optional = true, version = "0.0.23", package = "uu_pwd", path = "src/uu/pwd" } +readlink = { optional = true, version = "0.0.23", package = "uu_readlink", path = "src/uu/readlink" } +realpath = { optional = true, version = "0.0.23", package = "uu_realpath", path = "src/uu/realpath" } +rm = { optional = true, version = "0.0.23", package = "uu_rm", path = "src/uu/rm" } +rmdir = { optional = true, version = "0.0.23", package = "uu_rmdir", path = "src/uu/rmdir" } +runcon = { optional = true, version = "0.0.23", package = "uu_runcon", path = "src/uu/runcon" } +seq = { optional = true, version = "0.0.23", package = "uu_seq", path = "src/uu/seq" } +shred = { optional = true, version = "0.0.23", package = "uu_shred", path = "src/uu/shred" } +shuf = { optional = true, version = "0.0.23", package = "uu_shuf", path = "src/uu/shuf" } +sleep = { optional = true, version = "0.0.23", package = "uu_sleep", path = "src/uu/sleep" } +sort = { optional = true, version = "0.0.23", package = "uu_sort", path = "src/uu/sort" } +split = { optional = true, version = "0.0.23", package = "uu_split", path = "src/uu/split" } +stat = { optional = true, version = "0.0.23", package = "uu_stat", path = "src/uu/stat" } +stdbuf = { optional = true, version = "0.0.23", package = "uu_stdbuf", path = "src/uu/stdbuf" } +stty = { optional = true, version = "0.0.23", package = "uu_stty", path = "src/uu/stty" } +sum = { optional = true, version = "0.0.23", package = "uu_sum", path = "src/uu/sum" } +sync = { optional = true, version = "0.0.23", package = "uu_sync", path = "src/uu/sync" } +tac = { optional = true, version = "0.0.23", package = "uu_tac", path = "src/uu/tac" } +tail = { optional = true, version = "0.0.23", package = "uu_tail", path = "src/uu/tail" } +tee = { optional = true, version = "0.0.23", package = "uu_tee", path = "src/uu/tee" } +timeout = { optional = true, version = "0.0.23", package = "uu_timeout", path = "src/uu/timeout" } +touch = { optional = true, version = "0.0.23", package = "uu_touch", path = "src/uu/touch" } +tr = { optional = true, version = "0.0.23", package = "uu_tr", path = "src/uu/tr" } +true = { optional = true, version = "0.0.23", package = "uu_true", path = "src/uu/true" } +truncate = { optional = true, version = "0.0.23", package = "uu_truncate", path = "src/uu/truncate" } +tsort = { optional = true, version = "0.0.23", package = "uu_tsort", path = "src/uu/tsort" } +tty = { optional = true, version = "0.0.23", package = "uu_tty", path = "src/uu/tty" } +uname = { optional = true, version = "0.0.23", package = "uu_uname", path = "src/uu/uname" } +unexpand = { optional = true, version = "0.0.23", package = "uu_unexpand", path = "src/uu/unexpand" } +uniq = { optional = true, version = "0.0.23", package = "uu_uniq", path = "src/uu/uniq" } +unlink = { optional = true, version = "0.0.23", package = "uu_unlink", path = "src/uu/unlink" } +uptime = { optional = true, version = "0.0.23", package = "uu_uptime", path = "src/uu/uptime" } +users = { optional = true, version = "0.0.23", package = "uu_users", path = "src/uu/users" } +vdir = { optional = true, version = "0.0.23", package = "uu_vdir", path = "src/uu/vdir" } +wc = { optional = true, version = "0.0.23", package = "uu_wc", path = "src/uu/wc" } +who = { optional = true, version = "0.0.23", package = "uu_who", path = "src/uu/who" } +whoami = { optional = true, version = "0.0.23", package = "uu_whoami", path = "src/uu/whoami" } +yes = { optional = true, version = "0.0.23", package = "uu_yes", path = "src/uu/yes" } # this breaks clippy linting with: "tests/by-util/test_factor_benches.rs: No such file or directory (os error 2)" # factor_benches = { optional = true, version = "0.0.0", package = "uu_factor_benches", path = "tests/benches/factor" } diff --git a/src/uu/arch/Cargo.toml b/src/uu/arch/Cargo.toml index 9686c3f0acd..edb15f846c2 100644 --- a/src/uu/arch/Cargo.toml +++ b/src/uu/arch/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_arch" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "arch ~ (uutils) display machine architecture" diff --git a/src/uu/base32/Cargo.toml b/src/uu/base32/Cargo.toml index f3bca4a1972..71fbe325fe8 100644 --- a/src/uu/base32/Cargo.toml +++ b/src/uu/base32/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_base32" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "base32 ~ (uutils) decode/encode input (base32-encoding)" diff --git a/src/uu/base64/Cargo.toml b/src/uu/base64/Cargo.toml index ddd669b89b6..e52665bb692 100644 --- a/src/uu/base64/Cargo.toml +++ b/src/uu/base64/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_base64" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "base64 ~ (uutils) decode/encode input (base64-encoding)" diff --git a/src/uu/basename/Cargo.toml b/src/uu/basename/Cargo.toml index e74ad761635..3de240e04a9 100644 --- a/src/uu/basename/Cargo.toml +++ b/src/uu/basename/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_basename" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "basename ~ (uutils) display PATHNAME with leading directory components removed" diff --git a/src/uu/basenc/Cargo.toml b/src/uu/basenc/Cargo.toml index 7f418844070..54a5a53a118 100644 --- a/src/uu/basenc/Cargo.toml +++ b/src/uu/basenc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_basenc" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "basenc ~ (uutils) decode/encode input" diff --git a/src/uu/cat/Cargo.toml b/src/uu/cat/Cargo.toml index 33488cd072d..14383895e7d 100644 --- a/src/uu/cat/Cargo.toml +++ b/src/uu/cat/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_cat" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "cat ~ (uutils) concatenate and display input" diff --git a/src/uu/chcon/Cargo.toml b/src/uu/chcon/Cargo.toml index 07bd73f4b4a..d21da4cf04d 100644 --- a/src/uu/chcon/Cargo.toml +++ b/src/uu/chcon/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_chcon" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "chcon ~ (uutils) change file security context" diff --git a/src/uu/chgrp/Cargo.toml b/src/uu/chgrp/Cargo.toml index 9591e13651a..79942033f1e 100644 --- a/src/uu/chgrp/Cargo.toml +++ b/src/uu/chgrp/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_chgrp" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "chgrp ~ (uutils) change the group ownership of FILE" diff --git a/src/uu/chmod/Cargo.toml b/src/uu/chmod/Cargo.toml index d10d0e08dac..ebfe00fe1c5 100644 --- a/src/uu/chmod/Cargo.toml +++ b/src/uu/chmod/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_chmod" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "chmod ~ (uutils) change mode of FILE" diff --git a/src/uu/chown/Cargo.toml b/src/uu/chown/Cargo.toml index bbd75db0321..aab8f20b6c1 100644 --- a/src/uu/chown/Cargo.toml +++ b/src/uu/chown/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_chown" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "chown ~ (uutils) change the ownership of FILE" diff --git a/src/uu/chroot/Cargo.toml b/src/uu/chroot/Cargo.toml index 847b18ef5e2..a78c89685bf 100644 --- a/src/uu/chroot/Cargo.toml +++ b/src/uu/chroot/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_chroot" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "chroot ~ (uutils) run COMMAND under a new root directory" diff --git a/src/uu/cksum/Cargo.toml b/src/uu/cksum/Cargo.toml index e0d54edb9fc..9a811fafdbc 100644 --- a/src/uu/cksum/Cargo.toml +++ b/src/uu/cksum/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_cksum" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "cksum ~ (uutils) display CRC and size of input" diff --git a/src/uu/comm/Cargo.toml b/src/uu/comm/Cargo.toml index 07430f9d9f1..71ed0ad717f 100644 --- a/src/uu/comm/Cargo.toml +++ b/src/uu/comm/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_comm" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "comm ~ (uutils) compare sorted inputs" diff --git a/src/uu/cp/Cargo.toml b/src/uu/cp/Cargo.toml index 49ba75ca089..179b4668e68 100644 --- a/src/uu/cp/Cargo.toml +++ b/src/uu/cp/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_cp" -version = "0.0.22" +version = "0.0.23" authors = [ "Jordy Dickinson ", "Joshua S. Miller ", diff --git a/src/uu/csplit/Cargo.toml b/src/uu/csplit/Cargo.toml index a30ebc4f67b..e68b1258253 100644 --- a/src/uu/csplit/Cargo.toml +++ b/src/uu/csplit/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_csplit" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "csplit ~ (uutils) Output pieces of FILE separated by PATTERN(s) to files 'xx00', 'xx01', ..., and output byte counts of each piece to standard output" diff --git a/src/uu/cut/Cargo.toml b/src/uu/cut/Cargo.toml index 695777a72c1..e572d987ec4 100644 --- a/src/uu/cut/Cargo.toml +++ b/src/uu/cut/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_cut" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "cut ~ (uutils) display byte/field columns of input lines" diff --git a/src/uu/date/Cargo.toml b/src/uu/date/Cargo.toml index cd860ce581b..c5682f83e5a 100644 --- a/src/uu/date/Cargo.toml +++ b/src/uu/date/Cargo.toml @@ -1,7 +1,7 @@ # spell-checker:ignore datetime [package] name = "uu_date" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "date ~ (uutils) display or set the current time" diff --git a/src/uu/dd/Cargo.toml b/src/uu/dd/Cargo.toml index f56e46eaa21..7db05b422f3 100644 --- a/src/uu/dd/Cargo.toml +++ b/src/uu/dd/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_dd" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "dd ~ (uutils) copy and convert files" diff --git a/src/uu/df/Cargo.toml b/src/uu/df/Cargo.toml index 2aae7bd551f..d074e6be74f 100644 --- a/src/uu/df/Cargo.toml +++ b/src/uu/df/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_df" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "df ~ (uutils) display file system information" diff --git a/src/uu/dir/Cargo.toml b/src/uu/dir/Cargo.toml index 0bca7bb9d80..628c0fb8cfe 100644 --- a/src/uu/dir/Cargo.toml +++ b/src/uu/dir/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_dir" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "shortcut to ls -C -b" diff --git a/src/uu/dircolors/Cargo.toml b/src/uu/dircolors/Cargo.toml index a4a101326a8..6099b5a8428 100644 --- a/src/uu/dircolors/Cargo.toml +++ b/src/uu/dircolors/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_dircolors" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "dircolors ~ (uutils) display commands to set LS_COLORS" diff --git a/src/uu/dirname/Cargo.toml b/src/uu/dirname/Cargo.toml index 67806986cc9..b093ce3c55c 100644 --- a/src/uu/dirname/Cargo.toml +++ b/src/uu/dirname/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_dirname" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "dirname ~ (uutils) display parent directory of PATHNAME" diff --git a/src/uu/du/Cargo.toml b/src/uu/du/Cargo.toml index 158fa97864f..5e87b2f4381 100644 --- a/src/uu/du/Cargo.toml +++ b/src/uu/du/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_du" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "du ~ (uutils) display disk usage" diff --git a/src/uu/echo/Cargo.toml b/src/uu/echo/Cargo.toml index 92d5d4924fc..e63977e57f7 100644 --- a/src/uu/echo/Cargo.toml +++ b/src/uu/echo/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_echo" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "echo ~ (uutils) display TEXT" diff --git a/src/uu/env/Cargo.toml b/src/uu/env/Cargo.toml index 78b133733ab..a1df3563b20 100644 --- a/src/uu/env/Cargo.toml +++ b/src/uu/env/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_env" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "env ~ (uutils) set each NAME to VALUE in the environment and run COMMAND" diff --git a/src/uu/expand/Cargo.toml b/src/uu/expand/Cargo.toml index c329e61b6b2..63425bc5ea1 100644 --- a/src/uu/expand/Cargo.toml +++ b/src/uu/expand/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_expand" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "expand ~ (uutils) convert input tabs to spaces" diff --git a/src/uu/expr/Cargo.toml b/src/uu/expr/Cargo.toml index 3f0b5ca76ad..a867c27c80a 100644 --- a/src/uu/expr/Cargo.toml +++ b/src/uu/expr/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_expr" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "expr ~ (uutils) display the value of EXPRESSION" diff --git a/src/uu/factor/Cargo.toml b/src/uu/factor/Cargo.toml index 8ecee86c577..aee67eeeaf0 100644 --- a/src/uu/factor/Cargo.toml +++ b/src/uu/factor/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_factor" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "factor ~ (uutils) display the prime factors of each NUMBER" diff --git a/src/uu/false/Cargo.toml b/src/uu/false/Cargo.toml index 3b1ca97f968..50b5bc027d8 100644 --- a/src/uu/false/Cargo.toml +++ b/src/uu/false/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_false" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "false ~ (uutils) do nothing and fail" diff --git a/src/uu/fmt/Cargo.toml b/src/uu/fmt/Cargo.toml index c49f52741b4..688525e3b00 100644 --- a/src/uu/fmt/Cargo.toml +++ b/src/uu/fmt/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_fmt" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "fmt ~ (uutils) reformat each paragraph of input" diff --git a/src/uu/fold/Cargo.toml b/src/uu/fold/Cargo.toml index af36f0466ee..1887f1bbd3a 100644 --- a/src/uu/fold/Cargo.toml +++ b/src/uu/fold/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_fold" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "fold ~ (uutils) wrap each line of input" diff --git a/src/uu/groups/Cargo.toml b/src/uu/groups/Cargo.toml index 5f786acfd9f..9c513517500 100644 --- a/src/uu/groups/Cargo.toml +++ b/src/uu/groups/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_groups" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "groups ~ (uutils) display group memberships for USERNAME" diff --git a/src/uu/hashsum/Cargo.toml b/src/uu/hashsum/Cargo.toml index 23b60a7014a..1ecb5f8403c 100644 --- a/src/uu/hashsum/Cargo.toml +++ b/src/uu/hashsum/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_hashsum" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "hashsum ~ (uutils) display or check input digests" diff --git a/src/uu/head/Cargo.toml b/src/uu/head/Cargo.toml index 7c73f3941a7..12872c80820 100644 --- a/src/uu/head/Cargo.toml +++ b/src/uu/head/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_head" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "head ~ (uutils) display the first lines of input" diff --git a/src/uu/hostid/Cargo.toml b/src/uu/hostid/Cargo.toml index 2e6dcc33952..602e7ef3751 100644 --- a/src/uu/hostid/Cargo.toml +++ b/src/uu/hostid/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_hostid" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "hostid ~ (uutils) display the numeric identifier of the current host" diff --git a/src/uu/hostname/Cargo.toml b/src/uu/hostname/Cargo.toml index 49cb14bf2b1..a9b033d123b 100644 --- a/src/uu/hostname/Cargo.toml +++ b/src/uu/hostname/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_hostname" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "hostname ~ (uutils) display or set the host name of the current host" diff --git a/src/uu/id/Cargo.toml b/src/uu/id/Cargo.toml index bf1eaf44ca5..b62df1eebd2 100644 --- a/src/uu/id/Cargo.toml +++ b/src/uu/id/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_id" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "id ~ (uutils) display user and group information for USER" diff --git a/src/uu/install/Cargo.toml b/src/uu/install/Cargo.toml index b5353eace6a..0110380068b 100644 --- a/src/uu/install/Cargo.toml +++ b/src/uu/install/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_install" -version = "0.0.22" +version = "0.0.23" authors = ["Ben Eills ", "uutils developers"] license = "MIT" description = "install ~ (uutils) copy files from SOURCE to DESTINATION (with specified attributes)" diff --git a/src/uu/join/Cargo.toml b/src/uu/join/Cargo.toml index c52f66de44e..20853527d83 100644 --- a/src/uu/join/Cargo.toml +++ b/src/uu/join/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_join" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "join ~ (uutils) merge lines from inputs with matching join fields" diff --git a/src/uu/kill/Cargo.toml b/src/uu/kill/Cargo.toml index a606c81e745..6cb8cee6e3d 100644 --- a/src/uu/kill/Cargo.toml +++ b/src/uu/kill/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_kill" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "kill ~ (uutils) send a signal to a process" diff --git a/src/uu/link/Cargo.toml b/src/uu/link/Cargo.toml index c79ca604199..cba12c95c3b 100644 --- a/src/uu/link/Cargo.toml +++ b/src/uu/link/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_link" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "link ~ (uutils) create a hard (file system) link to FILE" diff --git a/src/uu/ln/Cargo.toml b/src/uu/ln/Cargo.toml index e9f0e6eb749..4243075ea0a 100644 --- a/src/uu/ln/Cargo.toml +++ b/src/uu/ln/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_ln" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "ln ~ (uutils) create a (file system) link to TARGET" diff --git a/src/uu/logname/Cargo.toml b/src/uu/logname/Cargo.toml index 41cbcf73b03..2e8553f12c4 100644 --- a/src/uu/logname/Cargo.toml +++ b/src/uu/logname/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_logname" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "logname ~ (uutils) display the login name of the current user" diff --git a/src/uu/ls/Cargo.toml b/src/uu/ls/Cargo.toml index 64d1cf1364d..96cf7df1a0d 100644 --- a/src/uu/ls/Cargo.toml +++ b/src/uu/ls/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_ls" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "ls ~ (uutils) display directory contents" diff --git a/src/uu/mkdir/Cargo.toml b/src/uu/mkdir/Cargo.toml index c0d60dc9d50..4f6ddb3cc02 100644 --- a/src/uu/mkdir/Cargo.toml +++ b/src/uu/mkdir/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_mkdir" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "mkdir ~ (uutils) create DIRECTORY" diff --git a/src/uu/mkfifo/Cargo.toml b/src/uu/mkfifo/Cargo.toml index 285fc2b3429..d7f787511a8 100644 --- a/src/uu/mkfifo/Cargo.toml +++ b/src/uu/mkfifo/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_mkfifo" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "mkfifo ~ (uutils) create FIFOs (named pipes)" diff --git a/src/uu/mknod/Cargo.toml b/src/uu/mknod/Cargo.toml index 2e660171473..58caa5d9dc4 100644 --- a/src/uu/mknod/Cargo.toml +++ b/src/uu/mknod/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_mknod" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "mknod ~ (uutils) create special file NAME of TYPE" diff --git a/src/uu/mktemp/Cargo.toml b/src/uu/mktemp/Cargo.toml index 8d8a885b158..2913e4b6e02 100644 --- a/src/uu/mktemp/Cargo.toml +++ b/src/uu/mktemp/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_mktemp" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "mktemp ~ (uutils) create and display a temporary file or directory from TEMPLATE" diff --git a/src/uu/more/Cargo.toml b/src/uu/more/Cargo.toml index 5614a0495a3..9f0bc9d3d65 100644 --- a/src/uu/more/Cargo.toml +++ b/src/uu/more/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_more" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "more ~ (uutils) input perusal filter" diff --git a/src/uu/mv/Cargo.toml b/src/uu/mv/Cargo.toml index 0be31ad7254..83a10ef6b7a 100644 --- a/src/uu/mv/Cargo.toml +++ b/src/uu/mv/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_mv" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "mv ~ (uutils) move (rename) SOURCE to DESTINATION" diff --git a/src/uu/nice/Cargo.toml b/src/uu/nice/Cargo.toml index 465b7b76595..6fab5cefc24 100644 --- a/src/uu/nice/Cargo.toml +++ b/src/uu/nice/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_nice" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "nice ~ (uutils) run PROGRAM with modified scheduling priority" diff --git a/src/uu/nl/Cargo.toml b/src/uu/nl/Cargo.toml index 003e1791385..56cb8d8e94e 100644 --- a/src/uu/nl/Cargo.toml +++ b/src/uu/nl/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_nl" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "nl ~ (uutils) display input with added line numbers" diff --git a/src/uu/nohup/Cargo.toml b/src/uu/nohup/Cargo.toml index 41c98982af0..24d8f659c7e 100644 --- a/src/uu/nohup/Cargo.toml +++ b/src/uu/nohup/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_nohup" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "nohup ~ (uutils) run COMMAND, ignoring hangup signals" diff --git a/src/uu/nproc/Cargo.toml b/src/uu/nproc/Cargo.toml index 0acc0e95113..54bb1d83c09 100644 --- a/src/uu/nproc/Cargo.toml +++ b/src/uu/nproc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_nproc" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "nproc ~ (uutils) display the number of processing units available" diff --git a/src/uu/numfmt/Cargo.toml b/src/uu/numfmt/Cargo.toml index cf2f0864606..79e6d5401aa 100644 --- a/src/uu/numfmt/Cargo.toml +++ b/src/uu/numfmt/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_numfmt" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "numfmt ~ (uutils) reformat NUMBER" diff --git a/src/uu/od/Cargo.toml b/src/uu/od/Cargo.toml index b7799e05200..84c89ee8611 100644 --- a/src/uu/od/Cargo.toml +++ b/src/uu/od/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_od" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "od ~ (uutils) display formatted representation of input" diff --git a/src/uu/paste/Cargo.toml b/src/uu/paste/Cargo.toml index dd030478c09..68255c82ddd 100644 --- a/src/uu/paste/Cargo.toml +++ b/src/uu/paste/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_paste" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "paste ~ (uutils) merge lines from inputs" diff --git a/src/uu/pathchk/Cargo.toml b/src/uu/pathchk/Cargo.toml index 708ae2afbaf..dee85deae55 100644 --- a/src/uu/pathchk/Cargo.toml +++ b/src/uu/pathchk/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_pathchk" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "pathchk ~ (uutils) diagnose invalid or non-portable PATHNAME" diff --git a/src/uu/pinky/Cargo.toml b/src/uu/pinky/Cargo.toml index c4739e7fcb6..f64cb758510 100644 --- a/src/uu/pinky/Cargo.toml +++ b/src/uu/pinky/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_pinky" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "pinky ~ (uutils) display user information" diff --git a/src/uu/pr/Cargo.toml b/src/uu/pr/Cargo.toml index 9a12e10f5bf..ab54593837f 100644 --- a/src/uu/pr/Cargo.toml +++ b/src/uu/pr/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_pr" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "pr ~ (uutils) convert text files for printing" diff --git a/src/uu/printenv/Cargo.toml b/src/uu/printenv/Cargo.toml index 64435127f6e..a6d7c91d046 100644 --- a/src/uu/printenv/Cargo.toml +++ b/src/uu/printenv/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_printenv" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "printenv ~ (uutils) display value of environment VAR" diff --git a/src/uu/printf/Cargo.toml b/src/uu/printf/Cargo.toml index a4fd773da28..1cb05ec09b0 100644 --- a/src/uu/printf/Cargo.toml +++ b/src/uu/printf/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_printf" -version = "0.0.22" +version = "0.0.23" authors = ["Nathan Ross", "uutils developers"] license = "MIT" description = "printf ~ (uutils) FORMAT and display ARGUMENTS" diff --git a/src/uu/ptx/Cargo.toml b/src/uu/ptx/Cargo.toml index ea241600bce..4ea395dc5cc 100644 --- a/src/uu/ptx/Cargo.toml +++ b/src/uu/ptx/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_ptx" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "ptx ~ (uutils) display a permuted index of input" diff --git a/src/uu/pwd/Cargo.toml b/src/uu/pwd/Cargo.toml index 5f15245b5d4..8bd0ccece58 100644 --- a/src/uu/pwd/Cargo.toml +++ b/src/uu/pwd/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_pwd" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "pwd ~ (uutils) display current working directory" diff --git a/src/uu/readlink/Cargo.toml b/src/uu/readlink/Cargo.toml index a696abdfaa2..4d1574c50be 100644 --- a/src/uu/readlink/Cargo.toml +++ b/src/uu/readlink/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_readlink" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "readlink ~ (uutils) display resolved path of PATHNAME" diff --git a/src/uu/realpath/Cargo.toml b/src/uu/realpath/Cargo.toml index 6d207275322..8b21d6c7f8d 100644 --- a/src/uu/realpath/Cargo.toml +++ b/src/uu/realpath/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_realpath" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "realpath ~ (uutils) display resolved absolute path of PATHNAME" diff --git a/src/uu/rm/Cargo.toml b/src/uu/rm/Cargo.toml index a1b3999d4d4..34ada60e865 100644 --- a/src/uu/rm/Cargo.toml +++ b/src/uu/rm/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_rm" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "rm ~ (uutils) remove PATHNAME" diff --git a/src/uu/rmdir/Cargo.toml b/src/uu/rmdir/Cargo.toml index 8288309f258..f17db50b0ee 100644 --- a/src/uu/rmdir/Cargo.toml +++ b/src/uu/rmdir/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_rmdir" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "rmdir ~ (uutils) remove empty DIRECTORY" diff --git a/src/uu/runcon/Cargo.toml b/src/uu/runcon/Cargo.toml index 04d3aae7139..72237ece95a 100644 --- a/src/uu/runcon/Cargo.toml +++ b/src/uu/runcon/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_runcon" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "runcon ~ (uutils) run command with specified security context" diff --git a/src/uu/seq/Cargo.toml b/src/uu/seq/Cargo.toml index b8a5c084cde..95f761696c2 100644 --- a/src/uu/seq/Cargo.toml +++ b/src/uu/seq/Cargo.toml @@ -1,7 +1,7 @@ # spell-checker:ignore bigdecimal [package] name = "uu_seq" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "seq ~ (uutils) display a sequence of numbers" diff --git a/src/uu/shred/Cargo.toml b/src/uu/shred/Cargo.toml index 0e7ad2ad317..8721cd624f0 100644 --- a/src/uu/shred/Cargo.toml +++ b/src/uu/shred/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_shred" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "shred ~ (uutils) hide former FILE contents with repeated overwrites" diff --git a/src/uu/shuf/Cargo.toml b/src/uu/shuf/Cargo.toml index 6a86c6d9858..2bbf2af149d 100644 --- a/src/uu/shuf/Cargo.toml +++ b/src/uu/shuf/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_shuf" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "shuf ~ (uutils) display random permutations of input lines" diff --git a/src/uu/sleep/Cargo.toml b/src/uu/sleep/Cargo.toml index 6ca686c78ce..d3d1fe61e9f 100644 --- a/src/uu/sleep/Cargo.toml +++ b/src/uu/sleep/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_sleep" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "sleep ~ (uutils) pause for DURATION" diff --git a/src/uu/sort/Cargo.toml b/src/uu/sort/Cargo.toml index 0cbafc5cfac..8c1c03dc638 100644 --- a/src/uu/sort/Cargo.toml +++ b/src/uu/sort/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_sort" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "sort ~ (uutils) sort input lines" diff --git a/src/uu/split/Cargo.toml b/src/uu/split/Cargo.toml index c5a34847324..6253dbf2be3 100644 --- a/src/uu/split/Cargo.toml +++ b/src/uu/split/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_split" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "split ~ (uutils) split input into output files" diff --git a/src/uu/stat/Cargo.toml b/src/uu/stat/Cargo.toml index 2974562f352..5ed91a88cb6 100644 --- a/src/uu/stat/Cargo.toml +++ b/src/uu/stat/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_stat" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "stat ~ (uutils) display FILE status" diff --git a/src/uu/stdbuf/Cargo.toml b/src/uu/stdbuf/Cargo.toml index 8168733affe..ed7a0e2e11e 100644 --- a/src/uu/stdbuf/Cargo.toml +++ b/src/uu/stdbuf/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_stdbuf" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "stdbuf ~ (uutils) run COMMAND with modified standard stream buffering" @@ -20,7 +20,7 @@ tempfile = { workspace = true } uucore = { workspace = true } [build-dependencies] -libstdbuf = { version = "0.0.22", package = "uu_stdbuf_libstdbuf", path = "src/libstdbuf" } +libstdbuf = { version = "0.0.23", package = "uu_stdbuf_libstdbuf", path = "src/libstdbuf" } [[bin]] name = "stdbuf" diff --git a/src/uu/stdbuf/src/libstdbuf/Cargo.toml b/src/uu/stdbuf/src/libstdbuf/Cargo.toml index 5fdfa27e035..be97c47aeaf 100644 --- a/src/uu/stdbuf/src/libstdbuf/Cargo.toml +++ b/src/uu/stdbuf/src/libstdbuf/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_stdbuf_libstdbuf" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "stdbuf/libstdbuf ~ (uutils); dynamic library required for stdbuf" diff --git a/src/uu/stty/Cargo.toml b/src/uu/stty/Cargo.toml index 965de5cd885..a353c66030f 100644 --- a/src/uu/stty/Cargo.toml +++ b/src/uu/stty/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_stty" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "stty ~ (uutils) print or change terminal characteristics" diff --git a/src/uu/sum/Cargo.toml b/src/uu/sum/Cargo.toml index 58fa7a08d4c..04be0fe72db 100644 --- a/src/uu/sum/Cargo.toml +++ b/src/uu/sum/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_sum" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "sum ~ (uutils) display checksum and block counts for input" diff --git a/src/uu/sync/Cargo.toml b/src/uu/sync/Cargo.toml index 2367561f873..4048115243a 100644 --- a/src/uu/sync/Cargo.toml +++ b/src/uu/sync/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_sync" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "sync ~ (uutils) synchronize cache writes to storage" diff --git a/src/uu/tac/Cargo.toml b/src/uu/tac/Cargo.toml index 1716a6d0363..164972c5f45 100644 --- a/src/uu/tac/Cargo.toml +++ b/src/uu/tac/Cargo.toml @@ -2,7 +2,7 @@ [package] name = "uu_tac" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "tac ~ (uutils) concatenate and display input lines in reverse order" diff --git a/src/uu/tail/Cargo.toml b/src/uu/tail/Cargo.toml index 66775c8d9d8..636480e08ba 100644 --- a/src/uu/tail/Cargo.toml +++ b/src/uu/tail/Cargo.toml @@ -1,7 +1,7 @@ # spell-checker:ignore (libs) kqueue fundu [package] name = "uu_tail" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "tail ~ (uutils) display the last lines of input" diff --git a/src/uu/tee/Cargo.toml b/src/uu/tee/Cargo.toml index 787c0cb326f..441b891a4ad 100644 --- a/src/uu/tee/Cargo.toml +++ b/src/uu/tee/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_tee" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "tee ~ (uutils) display input and copy to FILE" diff --git a/src/uu/test/Cargo.toml b/src/uu/test/Cargo.toml index 696fa366214..bc27511867e 100644 --- a/src/uu/test/Cargo.toml +++ b/src/uu/test/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_test" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "test ~ (uutils) evaluate comparison and file type expressions" diff --git a/src/uu/timeout/Cargo.toml b/src/uu/timeout/Cargo.toml index b6cb700a47f..4f6c062bb8a 100644 --- a/src/uu/timeout/Cargo.toml +++ b/src/uu/timeout/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_timeout" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "timeout ~ (uutils) run COMMAND with a DURATION time limit" diff --git a/src/uu/touch/Cargo.toml b/src/uu/touch/Cargo.toml index b44e8cf69ff..d67cd1f8a16 100644 --- a/src/uu/touch/Cargo.toml +++ b/src/uu/touch/Cargo.toml @@ -1,7 +1,7 @@ # spell-checker:ignore datetime [package] name = "uu_touch" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "touch ~ (uutils) change FILE timestamps" diff --git a/src/uu/tr/Cargo.toml b/src/uu/tr/Cargo.toml index c7e7bd60644..a07679dd187 100644 --- a/src/uu/tr/Cargo.toml +++ b/src/uu/tr/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_tr" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "tr ~ (uutils) translate characters within input and display" diff --git a/src/uu/true/Cargo.toml b/src/uu/true/Cargo.toml index ec6c5d2f909..8c4080724b6 100644 --- a/src/uu/true/Cargo.toml +++ b/src/uu/true/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_true" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "true ~ (uutils) do nothing and succeed" diff --git a/src/uu/truncate/Cargo.toml b/src/uu/truncate/Cargo.toml index a9cc179a67f..254a004e779 100644 --- a/src/uu/truncate/Cargo.toml +++ b/src/uu/truncate/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_truncate" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "truncate ~ (uutils) truncate (or extend) FILE to SIZE" diff --git a/src/uu/tsort/Cargo.toml b/src/uu/tsort/Cargo.toml index f72aeeda083..567d41e0b79 100644 --- a/src/uu/tsort/Cargo.toml +++ b/src/uu/tsort/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_tsort" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "tsort ~ (uutils) topologically sort input (partially ordered) pairs" diff --git a/src/uu/tty/Cargo.toml b/src/uu/tty/Cargo.toml index 66d4b42c795..95734e6c933 100644 --- a/src/uu/tty/Cargo.toml +++ b/src/uu/tty/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_tty" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "tty ~ (uutils) display the name of the terminal connected to standard input" diff --git a/src/uu/uname/Cargo.toml b/src/uu/uname/Cargo.toml index 10363b63cb0..cfd459c5bf1 100644 --- a/src/uu/uname/Cargo.toml +++ b/src/uu/uname/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_uname" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "uname ~ (uutils) display system information" diff --git a/src/uu/unexpand/Cargo.toml b/src/uu/unexpand/Cargo.toml index bab5b4e915b..459a67b9015 100644 --- a/src/uu/unexpand/Cargo.toml +++ b/src/uu/unexpand/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_unexpand" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "unexpand ~ (uutils) convert input spaces to tabs" diff --git a/src/uu/uniq/Cargo.toml b/src/uu/uniq/Cargo.toml index 2fb0552ce7b..efe5ca8ee53 100644 --- a/src/uu/uniq/Cargo.toml +++ b/src/uu/uniq/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_uniq" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "uniq ~ (uutils) filter identical adjacent lines from input" diff --git a/src/uu/unlink/Cargo.toml b/src/uu/unlink/Cargo.toml index aae56bf0bff..c6bfcd66bb7 100644 --- a/src/uu/unlink/Cargo.toml +++ b/src/uu/unlink/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_unlink" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "unlink ~ (uutils) remove a (file system) link to FILE" diff --git a/src/uu/uptime/Cargo.toml b/src/uu/uptime/Cargo.toml index f5fa083f9f5..899a30dfaa5 100644 --- a/src/uu/uptime/Cargo.toml +++ b/src/uu/uptime/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_uptime" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "uptime ~ (uutils) display dynamic system information" diff --git a/src/uu/users/Cargo.toml b/src/uu/users/Cargo.toml index d894ff4ba66..030c7d62551 100644 --- a/src/uu/users/Cargo.toml +++ b/src/uu/users/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_users" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "users ~ (uutils) display names of currently logged-in users" diff --git a/src/uu/vdir/Cargo.toml b/src/uu/vdir/Cargo.toml index b63f8c9eafc..d14e10d9565 100644 --- a/src/uu/vdir/Cargo.toml +++ b/src/uu/vdir/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_vdir" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "shortcut to ls -l -b" diff --git a/src/uu/wc/Cargo.toml b/src/uu/wc/Cargo.toml index 6a233c1ade3..8471472f867 100644 --- a/src/uu/wc/Cargo.toml +++ b/src/uu/wc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_wc" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "wc ~ (uutils) display newline, word, and byte counts for input" diff --git a/src/uu/who/Cargo.toml b/src/uu/who/Cargo.toml index fdfc0589757..249d5a8eeec 100644 --- a/src/uu/who/Cargo.toml +++ b/src/uu/who/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_who" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "who ~ (uutils) display information about currently logged-in users" diff --git a/src/uu/whoami/Cargo.toml b/src/uu/whoami/Cargo.toml index 59d011209fd..5336568334f 100644 --- a/src/uu/whoami/Cargo.toml +++ b/src/uu/whoami/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_whoami" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "whoami ~ (uutils) display user name of current effective user ID" diff --git a/src/uu/yes/Cargo.toml b/src/uu/yes/Cargo.toml index c36c2b1897d..dd562cab675 100644 --- a/src/uu/yes/Cargo.toml +++ b/src/uu/yes/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_yes" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "yes ~ (uutils) repeatedly display a line with STRING (or 'y')" diff --git a/src/uucore/Cargo.toml b/src/uucore/Cargo.toml index fb0b20338dc..370c8a3864c 100644 --- a/src/uucore/Cargo.toml +++ b/src/uucore/Cargo.toml @@ -2,7 +2,7 @@ [package] name = "uucore" -version = "0.0.22" +version = "0.0.23" authors = ["uutils developers"] license = "MIT" description = "uutils ~ 'core' uutils code library (cross-platform)" diff --git a/src/uucore_procs/Cargo.toml b/src/uucore_procs/Cargo.toml index 55d88096135..f6b92fe6353 100644 --- a/src/uucore_procs/Cargo.toml +++ b/src/uucore_procs/Cargo.toml @@ -1,7 +1,7 @@ # spell-checker:ignore uuhelp [package] name = "uucore_procs" -version = "0.0.22" +version = "0.0.23" authors = ["Roy Ivy III "] license = "MIT" description = "uutils ~ 'uucore' proc-macros" @@ -19,4 +19,4 @@ proc-macro = true [dependencies] proc-macro2 = "1.0" quote = "1.0" -uuhelp_parser = { path = "../uuhelp_parser", version = "0.0.22" } +uuhelp_parser = { path = "../uuhelp_parser", version = "0.0.23" } diff --git a/src/uuhelp_parser/Cargo.toml b/src/uuhelp_parser/Cargo.toml index 007f1ebb2c2..c07bce63160 100644 --- a/src/uuhelp_parser/Cargo.toml +++ b/src/uuhelp_parser/Cargo.toml @@ -1,7 +1,7 @@ # spell-checker:ignore uuhelp [package] name = "uuhelp_parser" -version = "0.0.22" +version = "0.0.23" edition = "2021" license = "MIT" description = "A collection of functions to parse the markdown code of help files" diff --git a/util/update-version.sh b/util/update-version.sh index bab1c4e0a18..244a7a7b29a 100755 --- a/util/update-version.sh +++ b/util/update-version.sh @@ -17,8 +17,8 @@ # 10) Create the release on github https://github.com/uutils/coreutils/releases/new # 11) Make sure we have good release notes -FROM="0.0.21" -TO="0.0.22" +FROM="0.0.22" +TO="0.0.23" PROGS=$(ls -1d src/uu/*/Cargo.toml src/uu/stdbuf/src/libstdbuf/Cargo.toml src/uucore/Cargo.toml Cargo.toml) From c4580df2a4cbb4f1cb4e3124779bde43d7b027f8 Mon Sep 17 00:00:00 2001 From: cswn Date: Tue, 14 Nov 2023 18:07:35 +0100 Subject: [PATCH 0345/2851] split: remove crash macro --- src/uu/split/src/platform/unix.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/uu/split/src/platform/unix.rs b/src/uu/split/src/platform/unix.rs index f4adb818834..c2bf7216b57 100644 --- a/src/uu/split/src/platform/unix.rs +++ b/src/uu/split/src/platform/unix.rs @@ -7,9 +7,10 @@ use std::io::Write; use std::io::{BufWriter, Error, ErrorKind, Result}; use std::path::Path; use std::process::{Child, Command, Stdio}; -use uucore::crash; +use uucore::error::USimpleError; use uucore::fs; use uucore::fs::FileInformation; +use uucore::show; /// A writer that writes to a shell_process' stdin /// @@ -101,10 +102,13 @@ impl Drop for FilterWriter { .expect("Couldn't wait for child process"); if let Some(return_code) = exit_status.code() { if return_code != 0 { - crash!(1, "Shell process returned {}", return_code); + show!(USimpleError::new( + 1, + format!("Shell process returned {}", return_code) + )); } } else { - crash!(1, "Shell process terminated by signal") + show!(USimpleError::new(1, "Shell process terminated by signal")); } } } From e2e5ec60cde2a407a2c8a467c1d0057f312e2276 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 14 Nov 2023 20:05:36 +0000 Subject: [PATCH 0346/2851] chore(deps): update rust crate itertools to 0.12.0 --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d83adfee600..c89c87df41c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1122,9 +1122,9 @@ dependencies = [ [[package]] name = "itertools" -version = "0.11.0" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +checksum = "25db6b064527c5d482d0423354fcd07a89a2dfe07b67892e62411946db7f07b0" dependencies = [ "either", ] diff --git a/Cargo.toml b/Cargo.toml index f313e2b03e4..2f3af2c83d1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -285,7 +285,7 @@ gcd = "2.3" glob = "0.3.1" half = "2.3" indicatif = "0.17" -itertools = "0.11.0" +itertools = "0.12.0" libc = "0.2.150" lscolors = { version = "0.15.0", default-features = false, features = [ "nu-ansi-term", From 6446ef294c1ec0dc9ab9450d659e51dc4b30526f Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Tue, 14 Nov 2023 20:44:19 +0100 Subject: [PATCH 0347/2851] publishing: check if the current version is already there or not This can happen when a publishing step failed --- util/publish.sh | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/util/publish.sh b/util/publish.sh index 71830f1f915..7207ba7fb91 100755 --- a/util/publish.sh +++ b/util/publish.sh @@ -5,6 +5,22 @@ if test "$1" != "--do-it"; then ARG="--dry-run --allow-dirty" fi +# Function to check if the crate is already published +is_already_published() { + local crate_name=$1 + local crate_version=$2 + + # Use the crates.io API to get the latest version of the crate + local latest_published_version + latest_published_version=$(curl -s https://crates.io/api/v1/crates/$crate_name | jq -r '.crate.max_version') + + if [ "$latest_published_version" = "$crate_version" ]; then + return 0 + else + return 1 + fi +} + # Figure out any dependencies between the util via Cargo.toml # We store this as edges in a graph with each line: # [dependent] [dependency] @@ -35,12 +51,19 @@ TOTAL_ORDER=$(echo -e $PARTIAL_ORDER | tsort | tac) # Remove the ROOT node from the start TOTAL_ORDER=${TOTAL_ORDER#ROOT} +CRATE_VERSION=$(grep '^version' Cargo.toml | head -n1 | cut -d '"' -f2) + set -e for dir in src/uuhelp_parser/ src/uucore_procs/ src/uucore/ src/uu/stdbuf/src/libstdbuf/; do ( cd "$dir" + CRATE_NAME=$(grep '^name =' "Cargo.toml" | head -n1 | cut -d '"' -f2) #shellcheck disable=SC2086 - cargo publish $ARG + if ! is_already_published "$CRATE_NAME" "$CRATE_VERSION"; then + cargo publish $ARG + else + echo "Skip: $CRATE_NAME $CRATE_VERSION already published" + fi ) sleep 2s done @@ -48,8 +71,13 @@ done for p in $TOTAL_ORDER; do ( cd "src/uu/$p" + CRATE_NAME=$(grep '^name =' "Cargo.toml" | head -n1 | cut -d '"' -f2) #shellcheck disable=SC2086 - cargo publish $ARG + if ! is_already_published "$CRATE_NAME" "$CRATE_VERSION"; then + cargo publish $ARG + else + echo "Skip: $CRATE_NAME $CRATE_VERSION already published" + fi ) done From 3f86bc59de6f8c6dbb73faaa81d3ad3fa40c01e7 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Wed, 15 Nov 2023 08:31:47 +0100 Subject: [PATCH 0348/2851] add missing features to uucore --- src/uu/dd/Cargo.toml | 2 +- src/uu/printf/Cargo.toml | 2 +- src/uu/seq/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/uu/dd/Cargo.toml b/src/uu/dd/Cargo.toml index 7db05b422f3..d654d829736 100644 --- a/src/uu/dd/Cargo.toml +++ b/src/uu/dd/Cargo.toml @@ -18,7 +18,7 @@ path = "src/dd.rs" clap = { workspace = true } gcd = { workspace = true } libc = { workspace = true } -uucore = { workspace = true, features = ["memo"] } +uucore = { workspace = true, features = ["memo", "quoting-style"] } [target.'cfg(any(target_os = "linux"))'.dependencies] nix = { workspace = true, features = ["fs"] } diff --git a/src/uu/printf/Cargo.toml b/src/uu/printf/Cargo.toml index 1cb05ec09b0..f36eff35e8c 100644 --- a/src/uu/printf/Cargo.toml +++ b/src/uu/printf/Cargo.toml @@ -16,7 +16,7 @@ path = "src/printf.rs" [dependencies] clap = { workspace = true } -uucore = { workspace = true, features = ["memo"] } +uucore = { workspace = true, features = ["memo", "quoting-style"] } [[bin]] name = "printf" diff --git a/src/uu/seq/Cargo.toml b/src/uu/seq/Cargo.toml index 95f761696c2..d4e7cd316c6 100644 --- a/src/uu/seq/Cargo.toml +++ b/src/uu/seq/Cargo.toml @@ -20,7 +20,7 @@ bigdecimal = { workspace = true } clap = { workspace = true } num-bigint = { workspace = true } num-traits = { workspace = true } -uucore = { workspace = true, features = ["memo"] } +uucore = { workspace = true, features = ["memo", "quoting-style"] } [[bin]] name = "seq" From 3f177ef97fc4e6f7a17f7565761732d89847fe72 Mon Sep 17 00:00:00 2001 From: "Y.D.X" <73375426+YDX-2147483647@users.noreply.github.com> Date: Wed, 15 Nov 2023 19:09:46 +0800 Subject: [PATCH 0349/2851] doc: Fix the markdown highlighting syntax MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There should be a new line after `[!WARNING]`, according to [community · Discussion #16925](https://github.com/orgs/community/discussions/16925). --- CONTRIBUTING.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 255ed2c53e3..b10d3d11472 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -14,7 +14,8 @@ check out these documents: Now follows a very important warning: -> [!WARNING] uutils is original code and cannot contain any code from GNU or +> [!WARNING] +> uutils is original code and cannot contain any code from GNU or > other implementations. This means that **we cannot accept any changes based on > the GNU source code**. To make sure that cannot happen, **you cannot link to > the GNU source code** either. From 94f6702ba52a875d251e869eba55583e61da9ad1 Mon Sep 17 00:00:00 2001 From: cswn Date: Wed, 15 Nov 2023 13:20:22 +0100 Subject: [PATCH 0350/2851] join: remove crash! macro --- src/uu/join/src/join.rs | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/uu/join/src/join.rs b/src/uu/join/src/join.rs index a48ba3657bd..3f2172da33e 100644 --- a/src/uu/join/src/join.rs +++ b/src/uu/join/src/join.rs @@ -21,7 +21,7 @@ use std::os::unix::ffi::OsStrExt; use uucore::display::Quotable; use uucore::error::{set_exit_code, UError, UResult, USimpleError}; use uucore::line_ending::LineEnding; -use uucore::{crash, crash_if_err, format_usage, help_about, help_usage}; +use uucore::{crash_if_err, format_usage, help_about, help_usage}; const ABOUT: &str = help_about!("join.md"); const USAGE: &str = help_usage!("join.md"); @@ -334,17 +334,23 @@ impl<'a> State<'a> { key: usize, line_ending: LineEnding, print_unpaired: bool, - ) -> State<'a> { + ) -> Result, JoinError> { let f = if name == "-" { Box::new(stdin.lock()) as Box } else { match File::open(name) { Ok(file) => Box::new(BufReader::new(file)) as Box, - Err(err) => crash!(1, "{}: {}", name.maybe_quote(), err), + Err(err) => { + return Err(JoinError::UnorderedInput(format!( + "{}: {}", + name.maybe_quote(), + err + ))); + } } }; - State { + Ok(State { key, file_name: name, file_num, @@ -355,7 +361,7 @@ impl<'a> State<'a> { line_num: 0, has_failed: false, has_unpaired: false, - } + }) } /// Skip the current unpaired line. @@ -847,7 +853,7 @@ fn exec(file1: &str, file2: &str, settings: Settings) -> Result<(), JoinError> { settings.key1, settings.line_ending, settings.print_unpaired1, - ); + )?; let mut state2 = State::new( FileNum::File2, @@ -856,7 +862,7 @@ fn exec(file1: &str, file2: &str, settings: Settings) -> Result<(), JoinError> { settings.key2, settings.line_ending, settings.print_unpaired2, - ); + )?; let input = Input::new( settings.separator, From 5dff5f2f736c44c576adce2dbcbe8275d52ebbd2 Mon Sep 17 00:00:00 2001 From: cswn Date: Wed, 15 Nov 2023 13:52:01 +0100 Subject: [PATCH 0351/2851] join: rename f variable to file_buf --- src/uu/join/src/join.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/uu/join/src/join.rs b/src/uu/join/src/join.rs index 3f2172da33e..c8008c91c61 100644 --- a/src/uu/join/src/join.rs +++ b/src/uu/join/src/join.rs @@ -335,7 +335,7 @@ impl<'a> State<'a> { line_ending: LineEnding, print_unpaired: bool, ) -> Result, JoinError> { - let f = if name == "-" { + let file_buf = if name == "-" { Box::new(stdin.lock()) as Box } else { match File::open(name) { @@ -355,7 +355,7 @@ impl<'a> State<'a> { file_name: name, file_num, print_unpaired, - lines: f.split(line_ending as u8), + lines: file_buf.split(line_ending as u8), max_len: 1, seq: Vec::new(), line_num: 0, From b3eae16faddf03e5ce83047f244a510932970565 Mon Sep 17 00:00:00 2001 From: ALXD Date: Wed, 15 Nov 2023 17:40:54 +0100 Subject: [PATCH 0352/2851] printf: intf: change warning and exit code --- .../features/tokenize/num_format/formatters/intf.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/uucore/src/lib/features/tokenize/num_format/formatters/intf.rs b/src/uucore/src/lib/features/tokenize/num_format/formatters/intf.rs index 11070113c26..767c0c4bb67 100644 --- a/src/uucore/src/lib/features/tokenize/num_format/formatters/intf.rs +++ b/src/uucore/src/lib/features/tokenize/num_format/formatters/intf.rs @@ -8,10 +8,11 @@ //! formatter for unsigned and signed int subs //! unsigned int: %X %x (hex u64) %o (octal u64) %u (base ten u64) //! signed int: %i %d (both base ten i64) +use crate::error::set_exit_code; +use crate::features::tokenize::num_format::num_format::warn_expected_numeric; + use super::super::format_field::FormatField; -use super::super::formatter::{ - get_it_at, warn_incomplete_conv, Base, FormatPrimitive, Formatter, InitialPrefix, -}; +use super::super::formatter::{get_it_at, Base, FormatPrimitive, Formatter, InitialPrefix}; use std::i64; use std::u64; @@ -112,7 +113,8 @@ impl Intf { } } _ => { - warn_incomplete_conv(str_in); + warn_expected_numeric(str_in); + set_exit_code(1); break; } } From 3cdb0966ae1b86724be8f7b6db2b6f41472169fc Mon Sep 17 00:00:00 2001 From: ALXD Date: Wed, 15 Nov 2023 17:46:58 +0100 Subject: [PATCH 0353/2851] printf: add a test for %x with invalid value --- tests/by-util/test_printf.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/by-util/test_printf.rs b/tests/by-util/test_printf.rs index a297dbf6833..ab3505a327b 100644 --- a/tests/by-util/test_printf.rs +++ b/tests/by-util/test_printf.rs @@ -258,6 +258,14 @@ fn sub_num_hex_upper() { .stdout_only("thirty in hex is 1E"); } +#[test] +fn sub_num_hex_non_numerical() { + new_ucmd!() + .args(&["parameters need to be numbers %X", "%194"]) + .fails() + .code_is(1); +} + #[test] fn sub_num_float() { new_ucmd!() From 212991cd53cba8be58e877ab5b17ea86eb46f20e Mon Sep 17 00:00:00 2001 From: "Y.D.X." <73375426+YDX-2147483647@users.noreply.github.com> Date: Thu, 16 Nov 2023 12:26:40 +0800 Subject: [PATCH 0354/2851] doc: Fix a broken link MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `/build.md` is absolute, so the link in https://uutils.github.io/coreutils/book/installation.html turns out to be https://uutils.github.io/build.html instead of https://uutils.github.io/coreutils/book/build.html. Reference: [Links · Markdown - mdBook Documentation](https://rust-lang.github.io/mdBook/format/markdown.html#links) --- docs/src/installation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/installation.md b/docs/src/installation.md index da124ead977..54b1e23f3f6 100644 --- a/docs/src/installation.md +++ b/docs/src/installation.md @@ -6,7 +6,7 @@ This is a list of uutils packages in various distributions and package managers. Note that these are packaged by third-parties and the packages might contain patches. -You can also [build uutils from source](/build.md). +You can also [build uutils from source](build.md). From a064c886566f810c3e58b1b1153762e772e35567 Mon Sep 17 00:00:00 2001 From: cswn Date: Thu, 16 Nov 2023 09:35:32 +0100 Subject: [PATCH 0355/2851] join: replace match with JoinError with map_err_context --- src/uu/join/src/join.rs | 34 ++++++++-------------------------- 1 file changed, 8 insertions(+), 26 deletions(-) diff --git a/src/uu/join/src/join.rs b/src/uu/join/src/join.rs index c8008c91c61..9661138879b 100644 --- a/src/uu/join/src/join.rs +++ b/src/uu/join/src/join.rs @@ -19,7 +19,7 @@ use std::num::IntErrorKind; #[cfg(unix)] use std::os::unix::ffi::OsStrExt; use uucore::display::Quotable; -use uucore::error::{set_exit_code, UError, UResult, USimpleError}; +use uucore::error::{set_exit_code, FromIo, UError, UResult, USimpleError}; use uucore::line_ending::LineEnding; use uucore::{crash_if_err, format_usage, help_about, help_usage}; @@ -334,20 +334,12 @@ impl<'a> State<'a> { key: usize, line_ending: LineEnding, print_unpaired: bool, - ) -> Result, JoinError> { + ) -> UResult> { let file_buf = if name == "-" { Box::new(stdin.lock()) as Box } else { - match File::open(name) { - Ok(file) => Box::new(BufReader::new(file)) as Box, - Err(err) => { - return Err(JoinError::UnorderedInput(format!( - "{}: {}", - name.maybe_quote(), - err - ))); - } - } + let file = File::open(name).map_err_context(|| format!("{}", name.maybe_quote()))?; + Box::new(BufReader::new(file)) as Box }; Ok(State { @@ -365,12 +357,7 @@ impl<'a> State<'a> { } /// Skip the current unpaired line. - fn skip_line( - &mut self, - writer: &mut impl Write, - input: &Input, - repr: &Repr, - ) -> Result<(), JoinError> { + fn skip_line(&mut self, writer: &mut impl Write, input: &Input, repr: &Repr) -> UResult<()> { if self.print_unpaired { self.print_first_line(writer, repr)?; } @@ -381,7 +368,7 @@ impl<'a> State<'a> { /// Keep reading line sequence until the key does not change, return /// the first line whose key differs. - fn extend(&mut self, input: &Input) -> Result, JoinError> { + fn extend(&mut self, input: &Input) -> UResult> { while let Some(line) = self.next_line(input)? { let diff = input.compare(self.get_current_key(), line.get_field(self.key)); @@ -490,12 +477,7 @@ impl<'a> State<'a> { 0 } - fn finalize( - &mut self, - writer: &mut impl Write, - input: &Input, - repr: &Repr, - ) -> Result<(), JoinError> { + fn finalize(&mut self, writer: &mut impl Write, input: &Input, repr: &Repr) -> UResult<()> { if self.has_line() { if self.print_unpaired { self.print_first_line(writer, repr)?; @@ -843,7 +825,7 @@ FILENUM is 1 or 2, corresponding to FILE1 or FILE2", ) } -fn exec(file1: &str, file2: &str, settings: Settings) -> Result<(), JoinError> { +fn exec(file1: &str, file2: &str, settings: Settings) -> UResult<()> { let stdin = stdin(); let mut state1 = State::new( From 7ff4cb3f4e236724d8bdf0d3a83258cd5daeb228 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Thu, 16 Nov 2023 10:40:31 +0100 Subject: [PATCH 0356/2851] update of the license file to make it generic (#5545) --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 49fdbd4cf5f..21bd44404e3 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) Jordi Boggiano and many others +Copyright (c) uutils developers Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in From 65dc70b55396c3a00b70143fc84cc7d96cc25539 Mon Sep 17 00:00:00 2001 From: cswn Date: Thu, 16 Nov 2023 12:02:39 +0100 Subject: [PATCH 0357/2851] join: remove match in uumain and return exec result --- src/uu/join/src/join.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/uu/join/src/join.rs b/src/uu/join/src/join.rs index 9661138879b..423af983ec9 100644 --- a/src/uu/join/src/join.rs +++ b/src/uu/join/src/join.rs @@ -701,10 +701,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { return Err(USimpleError::new(1, "both files cannot be standard input")); } - match exec(file1, file2, settings) { - Ok(_) => Ok(()), - Err(e) => Err(USimpleError::new(1, format!("{e}"))), - } + exec(file1, file2, settings) } pub fn uu_app() -> Command { From e7d58f673ff9515af11169cd92b6b341d6e8c13f Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Mon, 13 Nov 2023 17:37:25 +0100 Subject: [PATCH 0358/2851] seq: simplify and use new printf implementation --- src/uu/seq/src/extendedbigdecimal.rs | 54 +---- src/uu/seq/src/extendedbigint.rs | 214 ------------------ src/uu/seq/src/number.rs | 70 +----- src/uu/seq/src/numberparse.rs | 127 ++++------- src/uu/seq/src/seq.rs | 167 +++----------- src/uucore/src/lib/features/format/mod.rs | 4 +- .../src/lib/features/format/num_format.rs | 24 +- src/uucore/src/lib/features/format/spec.rs | 13 +- 8 files changed, 104 insertions(+), 569 deletions(-) delete mode 100644 src/uu/seq/src/extendedbigint.rs diff --git a/src/uu/seq/src/extendedbigdecimal.rs b/src/uu/seq/src/extendedbigdecimal.rs index 388046ba368..ecd460ceb73 100644 --- a/src/uu/seq/src/extendedbigdecimal.rs +++ b/src/uu/seq/src/extendedbigdecimal.rs @@ -25,13 +25,8 @@ use std::fmt::Display; use std::ops::Add; use bigdecimal::BigDecimal; -use num_bigint::BigInt; -use num_bigint::ToBigInt; -use num_traits::One; use num_traits::Zero; -use crate::extendedbigint::ExtendedBigInt; - #[derive(Debug, Clone)] pub enum ExtendedBigDecimal { /// Arbitrary precision floating point number. @@ -72,53 +67,14 @@ pub enum ExtendedBigDecimal { Nan, } -/// The smallest integer greater than or equal to this number. -fn ceil(x: BigDecimal) -> BigInt { - if x.is_integer() { - // Unwrapping the Option because it always returns Some - x.to_bigint().unwrap() - } else { - (x + BigDecimal::one().half()).round(0).to_bigint().unwrap() - } -} - -/// The largest integer less than or equal to this number. -fn floor(x: BigDecimal) -> BigInt { - if x.is_integer() { - // Unwrapping the Option because it always returns Some - x.to_bigint().unwrap() - } else { - (x - BigDecimal::one().half()).round(0).to_bigint().unwrap() - } -} - impl ExtendedBigDecimal { - /// The smallest integer greater than or equal to this number. - pub fn ceil(self) -> ExtendedBigInt { - match self { - Self::BigDecimal(x) => ExtendedBigInt::BigInt(ceil(x)), - other => From::from(other), - } + #[cfg(test)] + pub fn zero() -> Self { + Self::BigDecimal(1.into()) } - /// The largest integer less than or equal to this number. - pub fn floor(self) -> ExtendedBigInt { - match self { - Self::BigDecimal(x) => ExtendedBigInt::BigInt(floor(x)), - other => From::from(other), - } - } -} - -impl From for ExtendedBigDecimal { - fn from(big_int: ExtendedBigInt) -> Self { - match big_int { - ExtendedBigInt::BigInt(n) => Self::BigDecimal(BigDecimal::from(n)), - ExtendedBigInt::Infinity => Self::Infinity, - ExtendedBigInt::MinusInfinity => Self::MinusInfinity, - ExtendedBigInt::MinusZero => Self::MinusZero, - ExtendedBigInt::Nan => Self::Nan, - } + pub fn one() -> Self { + Self::BigDecimal(1.into()) } } diff --git a/src/uu/seq/src/extendedbigint.rs b/src/uu/seq/src/extendedbigint.rs deleted file mode 100644 index 6828fba2df2..00000000000 --- a/src/uu/seq/src/extendedbigint.rs +++ /dev/null @@ -1,214 +0,0 @@ -// This file is part of the uutils coreutils package. -// -// For the full copyright and license information, please view the LICENSE -// file that was distributed with this source code. -// spell-checker:ignore bigint extendedbigint extendedbigdecimal -//! An arbitrary precision integer that can also represent infinity, NaN, etc. -//! -//! Usually infinity, NaN, and negative zero are only represented for -//! floating point numbers. The [`ExtendedBigInt`] enumeration provides -//! a representation of those things with the set of integers. The -//! finite values are stored as [`BigInt`] instances. -//! -//! # Examples -//! -//! Addition works for [`ExtendedBigInt`] as it does for floats. For -//! example, adding infinity to any finite value results in infinity: -//! -//! ```rust,ignore -//! let summand1 = ExtendedBigInt::BigInt(BigInt::zero()); -//! let summand2 = ExtendedBigInt::Infinity; -//! assert_eq!(summand1 + summand2, ExtendedBigInt::Infinity); -//! ``` -use std::cmp::Ordering; -use std::fmt::Display; -use std::ops::Add; - -use num_bigint::BigInt; -use num_bigint::ToBigInt; -use num_traits::One; -use num_traits::Zero; - -use crate::extendedbigdecimal::ExtendedBigDecimal; - -#[derive(Debug, Clone)] -pub enum ExtendedBigInt { - BigInt(BigInt), - Infinity, - MinusInfinity, - MinusZero, - Nan, -} - -impl ExtendedBigInt { - /// The integer number one. - pub fn one() -> Self { - // We would like to implement `num_traits::One`, but it requires - // a multiplication implementation, and we don't want to - // implement that here. - Self::BigInt(BigInt::one()) - } -} - -impl From for ExtendedBigInt { - fn from(big_decimal: ExtendedBigDecimal) -> Self { - match big_decimal { - // TODO When can this fail? - ExtendedBigDecimal::BigDecimal(x) => Self::BigInt(x.to_bigint().unwrap()), - ExtendedBigDecimal::Infinity => Self::Infinity, - ExtendedBigDecimal::MinusInfinity => Self::MinusInfinity, - ExtendedBigDecimal::MinusZero => Self::MinusZero, - ExtendedBigDecimal::Nan => Self::Nan, - } - } -} - -impl Display for ExtendedBigInt { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::BigInt(n) => n.fmt(f), - Self::Infinity => f32::INFINITY.fmt(f), - Self::MinusInfinity => f32::NEG_INFINITY.fmt(f), - Self::MinusZero => "-0".fmt(f), - Self::Nan => "nan".fmt(f), - } - } -} - -impl Zero for ExtendedBigInt { - fn zero() -> Self { - Self::BigInt(BigInt::zero()) - } - fn is_zero(&self) -> bool { - match self { - Self::BigInt(n) => n.is_zero(), - Self::MinusZero => true, - _ => false, - } - } -} - -impl Add for ExtendedBigInt { - type Output = Self; - - fn add(self, other: Self) -> Self { - match (self, other) { - (Self::BigInt(m), Self::BigInt(n)) => Self::BigInt(m.add(n)), - (Self::BigInt(_), Self::MinusInfinity) => Self::MinusInfinity, - (Self::BigInt(_), Self::Infinity) => Self::Infinity, - (Self::BigInt(_), Self::Nan) => Self::Nan, - (Self::BigInt(m), Self::MinusZero) => Self::BigInt(m), - (Self::Infinity, Self::BigInt(_)) => Self::Infinity, - (Self::Infinity, Self::Infinity) => Self::Infinity, - (Self::Infinity, Self::MinusZero) => Self::Infinity, - (Self::Infinity, Self::MinusInfinity) => Self::Nan, - (Self::Infinity, Self::Nan) => Self::Nan, - (Self::MinusInfinity, Self::BigInt(_)) => Self::MinusInfinity, - (Self::MinusInfinity, Self::MinusInfinity) => Self::MinusInfinity, - (Self::MinusInfinity, Self::MinusZero) => Self::MinusInfinity, - (Self::MinusInfinity, Self::Infinity) => Self::Nan, - (Self::MinusInfinity, Self::Nan) => Self::Nan, - (Self::Nan, _) => Self::Nan, - (Self::MinusZero, other) => other, - } - } -} - -impl PartialEq for ExtendedBigInt { - fn eq(&self, other: &Self) -> bool { - match (self, other) { - (Self::BigInt(m), Self::BigInt(n)) => m.eq(n), - (Self::BigInt(_), Self::MinusInfinity) => false, - (Self::BigInt(_), Self::Infinity) => false, - (Self::BigInt(_), Self::Nan) => false, - (Self::BigInt(_), Self::MinusZero) => false, - (Self::Infinity, Self::BigInt(_)) => false, - (Self::Infinity, Self::Infinity) => true, - (Self::Infinity, Self::MinusZero) => false, - (Self::Infinity, Self::MinusInfinity) => false, - (Self::Infinity, Self::Nan) => false, - (Self::MinusInfinity, Self::BigInt(_)) => false, - (Self::MinusInfinity, Self::Infinity) => false, - (Self::MinusInfinity, Self::MinusZero) => false, - (Self::MinusInfinity, Self::MinusInfinity) => true, - (Self::MinusInfinity, Self::Nan) => false, - (Self::Nan, _) => false, - (Self::MinusZero, Self::BigInt(_)) => false, - (Self::MinusZero, Self::Infinity) => false, - (Self::MinusZero, Self::MinusZero) => true, - (Self::MinusZero, Self::MinusInfinity) => false, - (Self::MinusZero, Self::Nan) => false, - } - } -} - -impl PartialOrd for ExtendedBigInt { - fn partial_cmp(&self, other: &Self) -> Option { - match (self, other) { - (Self::BigInt(m), Self::BigInt(n)) => m.partial_cmp(n), - (Self::BigInt(_), Self::MinusInfinity) => Some(Ordering::Greater), - (Self::BigInt(_), Self::Infinity) => Some(Ordering::Less), - (Self::BigInt(_), Self::Nan) => None, - (Self::BigInt(m), Self::MinusZero) => m.partial_cmp(&BigInt::zero()), - (Self::Infinity, Self::BigInt(_)) => Some(Ordering::Greater), - (Self::Infinity, Self::Infinity) => Some(Ordering::Equal), - (Self::Infinity, Self::MinusZero) => Some(Ordering::Greater), - (Self::Infinity, Self::MinusInfinity) => Some(Ordering::Greater), - (Self::Infinity, Self::Nan) => None, - (Self::MinusInfinity, Self::BigInt(_)) => Some(Ordering::Less), - (Self::MinusInfinity, Self::Infinity) => Some(Ordering::Less), - (Self::MinusInfinity, Self::MinusZero) => Some(Ordering::Less), - (Self::MinusInfinity, Self::MinusInfinity) => Some(Ordering::Equal), - (Self::MinusInfinity, Self::Nan) => None, - (Self::Nan, _) => None, - (Self::MinusZero, Self::BigInt(n)) => BigInt::zero().partial_cmp(n), - (Self::MinusZero, Self::Infinity) => Some(Ordering::Less), - (Self::MinusZero, Self::MinusZero) => Some(Ordering::Equal), - (Self::MinusZero, Self::MinusInfinity) => Some(Ordering::Greater), - (Self::MinusZero, Self::Nan) => None, - } - } -} - -#[cfg(test)] -mod tests { - - use num_bigint::BigInt; - use num_traits::Zero; - - use crate::extendedbigint::ExtendedBigInt; - - #[test] - fn test_addition_infinity() { - let summand1 = ExtendedBigInt::BigInt(BigInt::zero()); - let summand2 = ExtendedBigInt::Infinity; - assert_eq!(summand1 + summand2, ExtendedBigInt::Infinity); - } - - #[test] - fn test_addition_minus_infinity() { - let summand1 = ExtendedBigInt::BigInt(BigInt::zero()); - let summand2 = ExtendedBigInt::MinusInfinity; - assert_eq!(summand1 + summand2, ExtendedBigInt::MinusInfinity); - } - - #[test] - fn test_addition_nan() { - let summand1 = ExtendedBigInt::BigInt(BigInt::zero()); - let summand2 = ExtendedBigInt::Nan; - let sum = summand1 + summand2; - match sum { - ExtendedBigInt::Nan => (), - _ => unreachable!(), - } - } - - #[test] - fn test_display() { - assert_eq!(format!("{}", ExtendedBigInt::BigInt(BigInt::zero())), "0"); - assert_eq!(format!("{}", ExtendedBigInt::MinusZero), "-0"); - assert_eq!(format!("{}", ExtendedBigInt::Infinity), "inf"); - assert_eq!(format!("{}", ExtendedBigInt::MinusInfinity), "-inf"); - assert_eq!(format!("{}", ExtendedBigInt::Nan), "nan"); - } -} diff --git a/src/uu/seq/src/number.rs b/src/uu/seq/src/number.rs index 85bc327ff46..4da1146eff6 100644 --- a/src/uu/seq/src/number.rs +++ b/src/uu/seq/src/number.rs @@ -12,70 +12,6 @@ use num_traits::Zero; use crate::extendedbigdecimal::ExtendedBigDecimal; -use crate::extendedbigint::ExtendedBigInt; - -/// An integral or floating point number. -#[derive(Debug, PartialEq)] -pub enum Number { - Int(ExtendedBigInt), - Float(ExtendedBigDecimal), -} - -impl Number { - /// Decide whether this number is zero (either positive or negative). - pub fn is_zero(&self) -> bool { - // We would like to implement `num_traits::Zero`, but it - // requires an addition implementation, and we don't want to - // implement that here. - match self { - Self::Int(n) => n.is_zero(), - Self::Float(x) => x.is_zero(), - } - } - - /// Convert this number into an `ExtendedBigDecimal`. - pub fn into_extended_big_decimal(self) -> ExtendedBigDecimal { - match self { - Self::Int(n) => ExtendedBigDecimal::from(n), - Self::Float(x) => x, - } - } - - /// The integer number one. - pub fn one() -> Self { - // We would like to implement `num_traits::One`, but it requires - // a multiplication implementation, and we don't want to - // implement that here. - Self::Int(ExtendedBigInt::one()) - } - - /// Round this number towards the given other number. - /// - /// If `other` is greater, then round up. If `other` is smaller, - /// then round down. - pub fn round_towards(self, other: &ExtendedBigInt) -> ExtendedBigInt { - match self { - // If this number is already an integer, it is already - // rounded to the nearest integer in the direction of - // `other`. - Self::Int(num) => num, - // Otherwise, if this number is a float, we need to decide - // whether `other` is larger or smaller than it, and thus - // whether to round up or round down, respectively. - Self::Float(num) => { - let other: ExtendedBigDecimal = From::from(other.clone()); - if other > num { - num.ceil() - } else { - // If they are equal, then `self` is already an - // integer, so calling `floor()` does no harm and - // will just return that integer anyway. - num.floor() - } - } - } - } -} /// A number with a specified number of integer and fractional digits. /// @@ -87,13 +23,13 @@ impl Number { /// You can get an instance of this struct by calling [`str::parse`]. #[derive(Debug)] pub struct PreciseNumber { - pub number: Number, + pub number: ExtendedBigDecimal, pub num_integral_digits: usize, pub num_fractional_digits: usize, } impl PreciseNumber { - pub fn new(number: Number, num_integral_digits: usize, num_fractional_digits: usize) -> Self { + pub fn new(number: ExtendedBigDecimal, num_integral_digits: usize, num_fractional_digits: usize) -> Self { Self { number, num_integral_digits, @@ -106,7 +42,7 @@ impl PreciseNumber { // We would like to implement `num_traits::One`, but it requires // a multiplication implementation, and we don't want to // implement that here. - Self::new(Number::one(), 1, 0) + Self::new(ExtendedBigDecimal::one(), 1, 0) } /// Decide whether this number is zero (either positive or negative). diff --git a/src/uu/seq/src/numberparse.rs b/src/uu/seq/src/numberparse.rs index 3f4b213955f..a82d1e88776 100644 --- a/src/uu/seq/src/numberparse.rs +++ b/src/uu/seq/src/numberparse.rs @@ -16,8 +16,6 @@ use num_traits::Num; use num_traits::Zero; use crate::extendedbigdecimal::ExtendedBigDecimal; -use crate::extendedbigint::ExtendedBigInt; -use crate::number::Number; use crate::number::PreciseNumber; /// An error returned when parsing a number fails. @@ -29,8 +27,8 @@ pub enum ParseNumberError { } /// Decide whether a given string and its parsed `BigInt` is negative zero. -fn is_minus_zero_int(s: &str, n: &BigInt) -> bool { - s.starts_with('-') && n == &BigInt::zero() +fn is_minus_zero_int(s: &str, n: &BigDecimal) -> bool { + s.starts_with('-') && n == &BigDecimal::zero() } /// Decide whether a given string and its parsed `BigDecimal` is negative zero. @@ -53,19 +51,19 @@ fn is_minus_zero_float(s: &str, x: &BigDecimal) -> bool { /// assert_eq!(actual, expected); /// ``` fn parse_no_decimal_no_exponent(s: &str) -> Result { - match s.parse::() { + match s.parse::() { Ok(n) => { // If `s` is '-0', then `parse()` returns `BigInt::zero()`, // but we need to return `Number::MinusZeroInt` instead. if is_minus_zero_int(s, &n) { Ok(PreciseNumber::new( - Number::Int(ExtendedBigInt::MinusZero), + ExtendedBigDecimal::MinusZero, s.len(), 0, )) } else { Ok(PreciseNumber::new( - Number::Int(ExtendedBigInt::BigInt(n)), + ExtendedBigDecimal::BigDecimal(n), s.len(), 0, )) @@ -79,7 +77,7 @@ fn parse_no_decimal_no_exponent(s: &str) -> Result return Err(ParseNumberError::Nan), _ => return Err(ParseNumberError::Float), }; - Ok(PreciseNumber::new(Number::Float(float_val), 0, 0)) + Ok(PreciseNumber::new(float_val, 0, 0)) } } } @@ -125,13 +123,13 @@ fn parse_exponent_no_decimal(s: &str, j: usize) -> Result Result() + .parse::() .map_err(|_| ParseNumberError::Float)?; Ok(PreciseNumber::new( - Number::Int(ExtendedBigInt::BigInt(n)), + ExtendedBigDecimal::BigDecimal(n), num_integral_digits, num_fractional_digits, )) } } else if is_minus_zero_float(s, &val) { Ok(PreciseNumber::new( - Number::Float(ExtendedBigDecimal::MinusZero), + ExtendedBigDecimal::MinusZero, num_integral_digits, num_fractional_digits, )) } else { Ok(PreciseNumber::new( - Number::Float(ExtendedBigDecimal::BigDecimal(val)), + ExtendedBigDecimal::BigDecimal(val), num_integral_digits, num_fractional_digits, )) @@ -303,20 +301,17 @@ fn parse_hexadecimal(s: &str) -> Result { } let num = BigInt::from_str_radix(s, 16).map_err(|_| ParseNumberError::Hex)?; + let num = BigDecimal::from(num); - match (is_neg, num == BigInt::zero()) { - (true, true) => Ok(PreciseNumber::new( - Number::Int(ExtendedBigInt::MinusZero), - 2, - 0, - )), + match (is_neg, num == BigDecimal::zero()) { + (true, true) => Ok(PreciseNumber::new(ExtendedBigDecimal::MinusZero, 2, 0)), (true, false) => Ok(PreciseNumber::new( - Number::Int(ExtendedBigInt::BigInt(-num)), + ExtendedBigDecimal::BigDecimal(-num), 0, 0, )), (false, _) => Ok(PreciseNumber::new( - Number::Int(ExtendedBigInt::BigInt(num)), + ExtendedBigDecimal::BigDecimal(num), 0, 0, )), @@ -364,19 +359,14 @@ impl FromStr for PreciseNumber { #[cfg(test)] mod tests { - use bigdecimal::BigDecimal; - use num_bigint::BigInt; - use num_traits::Zero; use crate::extendedbigdecimal::ExtendedBigDecimal; - use crate::extendedbigint::ExtendedBigInt; - use crate::number::Number; use crate::number::PreciseNumber; use crate::numberparse::ParseNumberError; /// Convenience function for parsing a [`Number`] and unwrapping. - fn parse(s: &str) -> Number { + fn parse(s: &str) -> ExtendedBigDecimal { s.parse::().unwrap().number } @@ -392,40 +382,37 @@ mod tests { #[test] fn test_parse_minus_zero_int() { - assert_eq!(parse("-0e0"), Number::Int(ExtendedBigInt::MinusZero)); - assert_eq!(parse("-0e-0"), Number::Int(ExtendedBigInt::MinusZero)); - assert_eq!(parse("-0e1"), Number::Int(ExtendedBigInt::MinusZero)); - assert_eq!(parse("-0e+1"), Number::Int(ExtendedBigInt::MinusZero)); - assert_eq!(parse("-0.0e1"), Number::Int(ExtendedBigInt::MinusZero)); - assert_eq!(parse("-0x0"), Number::Int(ExtendedBigInt::MinusZero)); + assert_eq!(parse("-0e0"), ExtendedBigDecimal::MinusZero); + assert_eq!(parse("-0e-0"), ExtendedBigDecimal::MinusZero); + assert_eq!(parse("-0e1"), ExtendedBigDecimal::MinusZero); + assert_eq!(parse("-0e+1"), ExtendedBigDecimal::MinusZero); + assert_eq!(parse("-0.0e1"), ExtendedBigDecimal::MinusZero); + assert_eq!(parse("-0x0"), ExtendedBigDecimal::MinusZero); } #[test] fn test_parse_minus_zero_float() { - assert_eq!(parse("-0.0"), Number::Float(ExtendedBigDecimal::MinusZero)); - assert_eq!(parse("-0e-1"), Number::Float(ExtendedBigDecimal::MinusZero)); - assert_eq!( - parse("-0.0e-1"), - Number::Float(ExtendedBigDecimal::MinusZero) - ); + assert_eq!(parse("-0.0"), ExtendedBigDecimal::MinusZero); + assert_eq!(parse("-0e-1"), ExtendedBigDecimal::MinusZero); + assert_eq!(parse("-0.0e-1"), ExtendedBigDecimal::MinusZero); } #[test] fn test_parse_big_int() { - assert_eq!(parse("0"), Number::Int(ExtendedBigInt::zero())); - assert_eq!(parse("0.1e1"), Number::Int(ExtendedBigInt::one())); + assert_eq!(parse("0"), ExtendedBigDecimal::zero()); + assert_eq!(parse("0.1e1"), ExtendedBigDecimal::one()); assert_eq!( parse("1.0e1"), - Number::Int(ExtendedBigInt::BigInt("10".parse::().unwrap())) + ExtendedBigDecimal::BigDecimal("10".parse::().unwrap()) ); } #[test] fn test_parse_hexadecimal_big_int() { - assert_eq!(parse("0x0"), Number::Int(ExtendedBigInt::zero())); + assert_eq!(parse("0x0"), ExtendedBigDecimal::zero()); assert_eq!( parse("0x10"), - Number::Int(ExtendedBigInt::BigInt("16".parse::().unwrap())) + ExtendedBigDecimal::BigDecimal("16".parse::().unwrap()) ); } @@ -433,56 +420,34 @@ mod tests { fn test_parse_big_decimal() { assert_eq!( parse("0.0"), - Number::Float(ExtendedBigDecimal::BigDecimal( - "0.0".parse::().unwrap() - )) + ExtendedBigDecimal::BigDecimal("0.0".parse::().unwrap()) ); assert_eq!( parse(".0"), - Number::Float(ExtendedBigDecimal::BigDecimal( - "0.0".parse::().unwrap() - )) + ExtendedBigDecimal::BigDecimal("0.0".parse::().unwrap()) ); assert_eq!( parse("1.0"), - Number::Float(ExtendedBigDecimal::BigDecimal( - "1.0".parse::().unwrap() - )) + ExtendedBigDecimal::BigDecimal("1.0".parse::().unwrap()) ); assert_eq!( parse("10e-1"), - Number::Float(ExtendedBigDecimal::BigDecimal( - "1.0".parse::().unwrap() - )) + ExtendedBigDecimal::BigDecimal("1.0".parse::().unwrap()) ); assert_eq!( parse("-1e-3"), - Number::Float(ExtendedBigDecimal::BigDecimal( - "-0.001".parse::().unwrap() - )) + ExtendedBigDecimal::BigDecimal("-0.001".parse::().unwrap()) ); } #[test] fn test_parse_inf() { - assert_eq!(parse("inf"), Number::Float(ExtendedBigDecimal::Infinity)); - assert_eq!( - parse("infinity"), - Number::Float(ExtendedBigDecimal::Infinity) - ); - assert_eq!(parse("+inf"), Number::Float(ExtendedBigDecimal::Infinity)); - assert_eq!( - parse("+infinity"), - Number::Float(ExtendedBigDecimal::Infinity) - ); - assert_eq!( - parse("-inf"), - Number::Float(ExtendedBigDecimal::MinusInfinity) - ); - assert_eq!( - parse("-infinity"), - Number::Float(ExtendedBigDecimal::MinusInfinity) - ); + assert_eq!(parse("inf"), ExtendedBigDecimal::Infinity); + assert_eq!(parse("infinity"), ExtendedBigDecimal::Infinity); + assert_eq!(parse("+inf"), ExtendedBigDecimal::Infinity); + assert_eq!(parse("+infinity"), ExtendedBigDecimal::Infinity); + assert_eq!(parse("-inf"), ExtendedBigDecimal::MinusInfinity); + assert_eq!(parse("-infinity"), ExtendedBigDecimal::MinusInfinity); } #[test] diff --git a/src/uu/seq/src/seq.rs b/src/uu/seq/src/seq.rs index bb4d5414ef8..a987405ce15 100644 --- a/src/uu/seq/src/seq.rs +++ b/src/uu/seq/src/seq.rs @@ -3,24 +3,21 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. // spell-checker:ignore (ToDO) istr chiter argptr ilen extendedbigdecimal extendedbigint numberparse -use std::io::{stdout, Write}; +use std::io::{stdout, ErrorKind, Write}; use clap::{crate_version, Arg, ArgAction, Command}; -use num_traits::{Zero, ToPrimitive}; +use num_traits::{ToPrimitive, Zero}; -use uucore::error::UResult; -use uucore::format::{printf, FormatArgument, Format, num_format}; +use uucore::error::{FromIo, UResult}; +use uucore::format::{num_format, Format}; use uucore::{format_usage, help_about, help_usage}; mod error; mod extendedbigdecimal; -mod extendedbigint; mod number; mod numberparse; use crate::error::SeqError; use crate::extendedbigdecimal::ExtendedBigDecimal; -use crate::extendedbigint::ExtendedBigInt; -use crate::number::Number; use crate::number::PreciseNumber; const ABOUT: &str = help_about!("seq.md"); @@ -41,11 +38,6 @@ struct SeqOptions<'a> { format: Option<&'a str>, } -/// A range of integers. -/// -/// The elements are (first, increment, last). -type RangeInt = (ExtendedBigInt, ExtendedBigInt, ExtendedBigInt); - /// A range of floats. /// /// The elements are (first, increment, last). @@ -116,53 +108,26 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { .num_fractional_digits .max(increment.num_fractional_digits); - let result = match (first.number, increment.number, last.number) { - (Number::Int(first), Number::Int(increment), last) => { - let last = last.round_towards(&first); - let format = match options.format { - Some(f) => { - let f = Format::::parse(f)?; - Some(f) - } - None => None, - }; - print_seq_integers( - (first, increment, last), - &options.separator, - &options.terminator, - options.equal_width, - padding, - format, - ) - } - (first, increment, last) => { - let format = match options.format { - Some(f) => { - let f = Format::::parse(f)?; - Some(f) - } - None => None, - }; - print_seq( - ( - first.into_extended_big_decimal(), - increment.into_extended_big_decimal(), - last.into_extended_big_decimal(), - ), - largest_dec, - &options.separator, - &options.terminator, - options.equal_width, - padding, - format, - ) + let format = match options.format { + Some(f) => { + let f = Format::::parse(f)?; + Some(f) } + None => None, }; + let result = print_seq( + (first.number, increment.number, last.number), + largest_dec, + &options.separator, + &options.terminator, + options.equal_width, + padding, + format, + ); match result { Ok(_) => Ok(()), - _ => todo!(), - // Err(err) if err.kind() == ErrorKind::BrokenPipe => Ok(()), - // Err(e) => Err(e.map_err_context(|| "write error".into())), + Err(err) if err.kind() == ErrorKind::BrokenPipe => Ok(()), + Err(e) => Err(e.map_err_context(|| "write error".into())), } } @@ -230,28 +195,6 @@ fn write_value_float( write!(writer, "{value_as_str}") } -/// Write a big int formatted according to the given parameters. -fn write_value_int( - writer: &mut impl Write, - value: &ExtendedBigInt, - width: usize, - pad: bool, -) -> std::io::Result<()> { - let value_as_str = if pad { - if *value == ExtendedBigInt::MinusZero { - format!("{value:00width$}") - } - } else { - format!("{value}") - }; - write!(writer, "{value_as_str}") -} - -// TODO `print_seq()` and `print_seq_integers()` are nearly identical, -// they could be refactored into a single more general function. - /// Floating point based code path fn print_seq( range: RangeFloat, @@ -261,12 +204,16 @@ fn print_seq( pad: bool, padding: usize, format: Option>, -) -> UResult<()> { +) -> std::io::Result<()> { let stdout = stdout(); let mut stdout = stdout.lock(); let (first, increment, last) = range; let mut value = first; - let padding = if pad { padding + 1 + largest_dec } else { 0 }; + let padding = if pad { + padding + if largest_dec > 0 { largest_dec + 1 } else { 0 } + } else { + 0 + }; let mut is_first_iteration = true; while !done_printing(&value, &increment, &last) { if !is_first_iteration { @@ -307,65 +254,3 @@ fn print_seq( stdout.flush()?; Ok(()) } - -/// Print an integer sequence. -/// -/// This function prints a sequence of integers defined by `range`, -/// which defines the first integer, last integer, and increment of the -/// range. The `separator` is inserted between each integer and -/// `terminator` is inserted at the end. -/// -/// The `pad` parameter indicates whether to pad numbers to the width -/// given in `padding`. -/// -/// If `is_first_minus_zero` is `true`, then the `first` parameter is -/// printed as if it were negative zero, even though no such number -/// exists as an integer (negative zero only exists for floating point -/// numbers). Only set this to `true` if `first` is actually zero. -fn print_seq_integers( - range: RangeInt, - separator: &str, - terminator: &str, - pad: bool, - padding: usize, - format: Option>, -) -> UResult<()> { - let stdout = stdout(); - let mut stdout = stdout.lock(); - let (first, increment, last) = range; - let mut value = first; - let mut is_first_iteration = true; - while !done_printing(&value, &increment, &last) { - if !is_first_iteration { - write!(stdout, "{separator}")?; - } - // If there was an argument `-f FORMAT`, then use that format - // template instead of the default formatting strategy. - // - // The `printf()` function takes in the template and - // the current value and writes the result to `stdout`. - // - // TODO See similar comment about formatting in `print_seq()`. - match &format { - Some(f) => { - let int = match &value { - ExtendedBigInt::BigInt(bi) => bi.to_i64().unwrap(), - ExtendedBigInt::Infinity => todo!(), - ExtendedBigInt::MinusInfinity => todo!(), - ExtendedBigInt::MinusZero => todo!(), - ExtendedBigInt::Nan => todo!(), - }; - f.fmt(&mut stdout, int)?; - } - None => write_value_int(&mut stdout, &value, padding, pad)?, - } - // TODO Implement augmenting addition. - value = value + increment.clone(); - is_first_iteration = false; - } - - if !is_first_iteration { - write!(stdout, "{terminator}")?; - } - Ok(()) -} diff --git a/src/uucore/src/lib/features/format/mod.rs b/src/uucore/src/lib/features/format/mod.rs index 48151be9892..d6500b20c5a 100644 --- a/src/uucore/src/lib/features/format/mod.rs +++ b/src/uucore/src/lib/features/format/mod.rs @@ -115,7 +115,7 @@ fn parse_iter(fmt: &[u8]) -> impl Iterator { let spec = match Spec::parse(&mut rest) { Some(spec) => spec, - None => return Some(Err(FormatError::SpecError)), + None => return Some(Err(dbg!(FormatError::SpecError))), }; Some(Ok(FormatItem::Spec(spec))) } @@ -230,7 +230,7 @@ impl Format { for item in &mut iter { match item? { FormatItem::Spec(_) => { - return Err(FormatError::SpecError); + return Err(dbg!(FormatError::SpecError)); } FormatItem::Text(t) => suffix.extend_from_slice(&t), FormatItem::Char(c) => suffix.push(c), diff --git a/src/uucore/src/lib/features/format/num_format.rs b/src/uucore/src/lib/features/format/num_format.rs index fd010bdc029..046249a13d2 100644 --- a/src/uucore/src/lib/features/format/num_format.rs +++ b/src/uucore/src/lib/features/format/num_format.rs @@ -13,14 +13,14 @@ pub trait Formatter { Self: Sized; } -#[derive(Clone, Copy)] +#[derive(Clone, Copy, Debug)] pub enum UnsignedIntVariant { Decimal, Octal(Prefix), Hexadecimal(Case, Prefix), } -#[derive(Clone, Copy)] +#[derive(Clone, Copy, Debug)] pub enum FloatVariant { Decimal, @@ -29,32 +29,32 @@ pub enum FloatVariant { Hexadecimal, } -#[derive(Clone, Copy, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Case { Lowercase, Uppercase, } -#[derive(Clone, Copy, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Prefix { No, Yes, } -#[derive(Clone, Copy, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum ForceDecimal { No, Yes, } -#[derive(Clone, Copy)] +#[derive(Clone, Copy, Debug)] pub enum PositiveSign { None, Plus, Space, } -#[derive(Clone, Copy)] +#[derive(Clone, Copy, Debug)] pub enum NumberAlignment { Left, RightSpace, @@ -93,7 +93,7 @@ impl Formatter for SignedInt { alignment, } = s else { - return Err(FormatError::SpecError); + return Err(dbg!(FormatError::SpecError)); }; let width = match width { @@ -152,7 +152,7 @@ impl Formatter for UnsignedInt { alignment, } = s else { - return Err(FormatError::SpecError); + return Err(dbg!(FormatError::SpecError)); }; let width = match width { @@ -241,19 +241,19 @@ impl Formatter for Float { precision, } = s else { - return Err(FormatError::SpecError); + return Err(dbg!(FormatError::SpecError)); }; let width = match width { Some(CanAsterisk::Fixed(x)) => x, None => 0, - Some(CanAsterisk::Asterisk) => return Err(FormatError::SpecError), + Some(CanAsterisk::Asterisk) => return Err(dbg!(FormatError::SpecError)), }; let precision = match precision { Some(CanAsterisk::Fixed(x)) => x, None => 0, - Some(CanAsterisk::Asterisk) => return Err(FormatError::SpecError), + Some(CanAsterisk::Asterisk) => return Err(dbg!(FormatError::SpecError)), }; Ok(Self { diff --git a/src/uucore/src/lib/features/format/spec.rs b/src/uucore/src/lib/features/format/spec.rs index 9c53669fa9c..abc9b7a875a 100644 --- a/src/uucore/src/lib/features/format/spec.rs +++ b/src/uucore/src/lib/features/format/spec.rs @@ -9,6 +9,7 @@ use super::{ }; use std::{fmt::Display, io::Write}; +#[derive(Debug)] pub enum Spec { Char { width: Option>, @@ -41,7 +42,7 @@ pub enum Spec { /// Precision and width specified might use an asterisk to indicate that they are /// determined by an argument. -#[derive(Clone, Copy)] +#[derive(Clone, Copy, Debug)] pub enum CanAsterisk { Fixed(T), Asterisk, @@ -99,6 +100,7 @@ impl Spec { let width = eat_asterisk_or_number(rest); let precision = if let Some(b'.') = rest.get(0) { + *rest = &rest[1..]; Some(eat_asterisk_or_number(rest).unwrap_or(CanAsterisk::Fixed(0))) } else { None @@ -134,7 +136,9 @@ impl Spec { *rest = &rest[1..]; } - Some(match rest.get(0)? { + let type_spec = rest.get(0)?; + *rest = &rest[1..]; + Some(match type_spec { b'c' => Spec::Char { width, align_left: minus, @@ -208,7 +212,10 @@ impl Spec { (false, false) => PositiveSign::None, }, }, - _ => return None, + x => { + dbg!("{:b}", x); + return dbg!(None) + }, }) } From 2f9fcf73faad9d60db6f08c2e9ecd57fa845b0bd Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Thu, 16 Nov 2023 16:02:38 +0100 Subject: [PATCH 0359/2851] clippy: fix warnings introduced by Rust 1.74 --- src/uu/more/src/more.rs | 2 +- tests/by-util/test_dd.rs | 4 ++-- tests/by-util/test_ls.rs | 6 +++--- tests/by-util/test_users.rs | 6 ++---- 4 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/uu/more/src/more.rs b/src/uu/more/src/more.rs index 02ed0feea20..b21b2ab1f35 100644 --- a/src/uu/more/src/more.rs +++ b/src/uu/more/src/more.rs @@ -88,7 +88,7 @@ impl Options { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { let args = args.collect_lossy(); - let matches = match uu_app().try_get_matches_from(&args) { + let matches = match uu_app().try_get_matches_from(args) { Ok(m) => m, Err(e) => return Err(e.into()), }; diff --git a/tests/by-util/test_dd.rs b/tests/by-util/test_dd.rs index f560e35261b..d5ac8dc801c 100644 --- a/tests/by-util/test_dd.rs +++ b/tests/by-util/test_dd.rs @@ -1470,7 +1470,7 @@ fn test_seek_output_fifo() { .args(&["count=0", "seek=1", "of=fifo", "status=noxfer"]) .run_no_wait(); - std::fs::write(at.plus("fifo"), &vec![0; 512]).unwrap(); + std::fs::write(at.plus("fifo"), vec![0; 512]).unwrap(); child .wait() @@ -1492,7 +1492,7 @@ fn test_skip_input_fifo() { .args(&["count=0", "skip=1", "if=fifo", "status=noxfer"]) .run_no_wait(); - std::fs::write(at.plus("fifo"), &vec![0; 512]).unwrap(); + std::fs::write(at.plus("fifo"), vec![0; 512]).unwrap(); child .wait() diff --git a/tests/by-util/test_ls.rs b/tests/by-util/test_ls.rs index cdd0292e1f2..07ea8c9cd63 100644 --- a/tests/by-util/test_ls.rs +++ b/tests/by-util/test_ls.rs @@ -994,9 +994,9 @@ fn test_ls_long() { fn test_ls_long_format() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; - at.mkdir(&at.plus_as_string("test-long-dir")); + at.mkdir(at.plus_as_string("test-long-dir")); at.touch(at.plus_as_string("test-long-dir/test-long-file")); - at.mkdir(&at.plus_as_string("test-long-dir/test-long-dir")); + at.mkdir(at.plus_as_string("test-long-dir/test-long-dir")); for arg in LONG_ARGS { // Assuming sane username do not have spaces within them. @@ -1971,7 +1971,7 @@ fn test_ls_color() { .join("nested_dir") .to_string_lossy() .to_string(); - at.mkdir(&nested_dir); + at.mkdir(nested_dir); at.mkdir("z"); let nested_file = Path::new("a") .join("nested_file") diff --git a/tests/by-util/test_users.rs b/tests/by-util/test_users.rs index 766378a9dca..3d87aa9d068 100644 --- a/tests/by-util/test_users.rs +++ b/tests/by-util/test_users.rs @@ -21,11 +21,9 @@ fn test_users_check_name() { #[cfg(target_os = "linux")] let util_name = util_name!(); #[cfg(target_vendor = "apple")] - let util_name = format!("g{}", util_name!()); + let util_name = &format!("g{}", util_name!()); - // note: clippy::needless_borrow *false positive* - #[allow(clippy::needless_borrow)] - let expected = TestScenario::new(&util_name) + let expected = TestScenario::new(util_name) .cmd(util_name) .env("LC_ALL", "C") .succeeds() From eaf500637900a47c4e00497aaccccf3d6d7dd5c8 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Thu, 16 Nov 2023 17:00:41 +0100 Subject: [PATCH 0360/2851] printf: parse arguments and handle escape codes --- src/uu/printf/src/printf.rs | 23 ++- .../src/lib/features/format/argument.rs | 60 ++++++ src/uucore/src/lib/features/format/escape.rs | 100 ++++++++++ src/uucore/src/lib/features/format/mod.rs | 186 +++++++++++------- .../src/lib/features/format/num_format.rs | 10 +- src/uucore/src/lib/features/format/spec.rs | 29 ++- 6 files changed, 317 insertions(+), 91 deletions(-) create mode 100644 src/uucore/src/lib/features/format/argument.rs create mode 100644 src/uucore/src/lib/features/format/escape.rs diff --git a/src/uu/printf/src/printf.rs b/src/uu/printf/src/printf.rs index 6e270ec2645..00d03816e4c 100644 --- a/src/uu/printf/src/printf.rs +++ b/src/uu/printf/src/printf.rs @@ -6,9 +6,12 @@ // spell-checker:ignore (change!) each's // spell-checker:ignore (ToDO) LONGHELP FORMATSTRING templating parameterizing formatstr +use std::io::stdout; +use std::ops::ControlFlow; + use clap::{crate_version, Arg, ArgAction, Command}; use uucore::error::{UResult, UUsageError}; -use uucore::format::{printf, FormatArgument}; +use uucore::format::{parse_spec_and_escape, FormatArgument}; use uucore::{format_usage, help_about, help_section, help_usage}; const VERSION: &str = "version"; @@ -30,12 +33,28 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let format_string = matches .get_one::(options::FORMATSTRING) .ok_or_else(|| UUsageError::new(1, "missing operand"))?; + let values: Vec<_> = match matches.get_many::(options::ARGUMENT) { Some(s) => s.map(|s| FormatArgument::Unparsed(s.to_string())).collect(), None => vec![], }; - printf(format_string, &values)?; + let mut args = values.iter().peekable(); + for item in parse_spec_and_escape(format_string.as_ref()) { + match item?.write(stdout(), &mut args)? { + ControlFlow::Continue(()) => {} + ControlFlow::Break(()) => break, + }; + } + + while args.peek().is_some() { + for item in parse_spec_and_escape(format_string.as_ref()) { + match item?.write(stdout(), &mut args)? { + ControlFlow::Continue(()) => {} + ControlFlow::Break(()) => break, + }; + } + } Ok(()) } diff --git a/src/uucore/src/lib/features/format/argument.rs b/src/uucore/src/lib/features/format/argument.rs new file mode 100644 index 00000000000..007f519c2a4 --- /dev/null +++ b/src/uucore/src/lib/features/format/argument.rs @@ -0,0 +1,60 @@ +#[derive(Clone, Debug)] +pub enum FormatArgument { + Char(char), + String(String), + UnsignedInt(u64), + SignedInt(i64), + Float(f64), + /// Special argument that gets coerced into the other variants + Unparsed(String), +} + +impl FormatArgument { + pub fn get_char(&self) -> Option { + match self { + Self::Char(c) => Some(*c), + Self::Unparsed(s) => { + let mut chars = s.chars(); + let Some(c) = chars.next() else { + return None; + }; + let None = chars.next() else { + return None; + }; + Some(c) + } + _ => None, + } + } + + pub fn get_u64(&self) -> Option { + match self { + Self::UnsignedInt(n) => Some(*n), + Self::Unparsed(s) => s.parse().ok(), + _ => None, + } + } + + pub fn get_i64(&self) -> Option { + match self { + Self::SignedInt(n) => Some(*n), + Self::Unparsed(s) => s.parse().ok(), + _ => None, + } + } + + pub fn get_f64(&self) -> Option { + match self { + Self::Float(n) => Some(*n), + Self::Unparsed(s) => s.parse().ok(), + _ => None, + } + } + + pub fn get_str(&self) -> Option<&str> { + match self { + Self::Unparsed(s) | Self::String(s) => Some(s), + _ => None, + } + } +} \ No newline at end of file diff --git a/src/uucore/src/lib/features/format/escape.rs b/src/uucore/src/lib/features/format/escape.rs new file mode 100644 index 00000000000..b8c21741caf --- /dev/null +++ b/src/uucore/src/lib/features/format/escape.rs @@ -0,0 +1,100 @@ +#[derive(Debug)] +pub enum EscapedChar { + Char(u8), + Backslash(u8), + End, +} + +#[repr(u8)] +#[derive(Clone, Copy)] +enum Base { + Oct = 8, + Hex = 16, +} + +impl Base { + fn max_digits(&self) -> u8 { + match self { + Self::Oct => 3, + Self::Hex => 2, + } + } + + fn to_digit(&self, c: u8) -> Option { + match self { + Base::Oct => { + if matches!(c, b'0'..=b'7') { + Some(c - b'0') + } else { + None + } + } + Base::Hex => match c { + b'0'..=b'9' => Some(c - b'0'), + b'A'..=b'F' => Some(c - b'A' + 10), + b'a'..=b'f' => Some(c - b'a' + 10), + _ => None, + }, + } + } +} + +/// Parse the numeric part of the `\xHHH` and `\0NNN` escape sequences +fn parse_code(input: &mut &[u8], base: Base) -> Option { + // All arithmetic on `ret` needs to be wrapping, because octal input can + // take 3 digits, which is 9 bits, and therefore more than what fits in a + // `u8`. GNU just seems to wrap these values. + // Note that if we instead make `ret` a `u32` and use `char::from_u32` will + // yield incorrect results because it will interpret values larger than + // `u8::MAX` as unicode. + let [c, rest @ ..] = input else { return None }; + let mut ret = base.to_digit(*c)?; + *input = &rest[..]; + + for _ in 1..base.max_digits() { + let [c, rest @ ..] = input else { break }; + let Some(n) = base.to_digit(*c) else { break }; + ret = ret.wrapping_mul(base as u8).wrapping_add(n); + *input = &rest[..]; + } + + Some(ret) +} + +pub fn parse_escape_code(rest: &mut &[u8]) -> EscapedChar { + if let [c, new_rest @ ..] = rest { + // This is for the \NNN syntax for octal sequences. + // Note that '0' is intentionally omitted because that + // would be the \0NNN syntax. + if let b'1'..=b'7' = c { + if let Some(parsed) = parse_code(rest, Base::Oct) { + return EscapedChar::Char(parsed); + } + } + + *rest = &new_rest[..]; + match c { + b'\\' => EscapedChar::Char(b'\\'), + b'a' => EscapedChar::Char(b'\x07'), + b'b' => EscapedChar::Char(b'\x08'), + b'c' => return EscapedChar::End, + b'e' => EscapedChar::Char(b'\x1b'), + b'f' => EscapedChar::Char(b'\x0c'), + b'n' => EscapedChar::Char(b'\n'), + b'r' => EscapedChar::Char(b'\r'), + b't' => EscapedChar::Char(b'\t'), + b'v' => EscapedChar::Char(b'\x0b'), + b'x' => { + if let Some(c) = parse_code(rest, Base::Hex) { + EscapedChar::Char(c) + } else { + EscapedChar::Backslash(b'x') + } + } + b'0' => EscapedChar::Char(parse_code(rest, Base::Oct).unwrap_or(b'\0')), + c => EscapedChar::Backslash(*c), + } + } else { + EscapedChar::Char(b'\\') + } +} diff --git a/src/uucore/src/lib/features/format/mod.rs b/src/uucore/src/lib/features/format/mod.rs index d6500b20c5a..8fa8d0717e1 100644 --- a/src/uucore/src/lib/features/format/mod.rs +++ b/src/uucore/src/lib/features/format/mod.rs @@ -8,8 +8,19 @@ //! [`Format`] struct, which represents a parsed format string. This reduces //! the need for parsing a format string multiple times and assures that no //! parsing errors occur during writing. +//! +//! There are three kinds of parsing that we might want to do: +//! +//! 1. Only `printf` specifiers (for e.g. `seq`, `dd`) +//! 2. Only escape sequences (for e.g. `echo`) +//! 3. Both `printf` specifiers and escape sequences (for e.g. `printf`) +//! +//! This module aims to combine all three use cases. + // spell-checker:ignore (vars) charf decf floatf intf scif strf Cninety +mod escape; +mod argument; pub mod num_format; mod spec; @@ -18,11 +29,16 @@ use std::{ error::Error, fmt::Display, io::{stdout, Write}, + ops::ControlFlow, }; +pub use argument::*; use crate::error::UError; -use self::num_format::Formatter; +use self::{ + escape::{parse_escape_code, EscapedChar}, + num_format::Formatter, +}; #[derive(Debug)] pub enum FormatError { @@ -54,80 +70,116 @@ impl Display for FormatError { } /// A single item to format -enum FormatItem { +pub enum FormatItem { /// A format specifier Spec(Spec), - /// Some plain text - Text(Vec), /// A single character - /// - /// Added in addition to `Text` as an optimization. - Char(u8), + Char(C), +} + +pub trait FormatChar { + fn write(&self, writer: impl Write) -> std::io::Result>; +} + +impl FormatChar for u8 { + fn write(&self, mut writer: impl Write) -> std::io::Result> { + writer.write(&[*self])?; + Ok(ControlFlow::Continue(())) + } } -#[derive(Clone, Debug)] -pub enum FormatArgument { - Char(char), - String(String), - UnsignedInt(u64), - SignedInt(i64), - Float(f64), - // Special argument that gets coerced into the other variants - Unparsed(String), +impl FormatChar for EscapedChar { + fn write(&self, mut writer: impl Write) -> std::io::Result> { + match self { + EscapedChar::Char(c) => { + writer.write(&[*c])?; + } + EscapedChar::Backslash(c) => { + writer.write(&[b'\\', *c])?; + } + EscapedChar::End => return Ok(ControlFlow::Break(())), + } + Ok(ControlFlow::Continue(())) + } } -impl FormatItem { - fn write<'a>( +impl FormatItem { + pub fn write<'a>( &self, - mut writer: impl Write, + writer: impl Write, args: &mut impl Iterator, - ) -> Result<(), FormatError> { + ) -> Result, FormatError> { match self { - FormatItem::Spec(spec) => spec.write(writer, args), - FormatItem::Text(bytes) => writer.write_all(bytes).map_err(FormatError::IoError), - FormatItem::Char(char) => writer.write_all(&[*char]).map_err(FormatError::IoError), - } + FormatItem::Spec(spec) => spec.write(writer, args)?, + FormatItem::Char(c) => return c.write(writer).map_err(FormatError::IoError), + }; + Ok(ControlFlow::Continue(())) } } -fn parse_iter(fmt: &[u8]) -> impl Iterator> + '_ { - let mut rest = fmt; - std::iter::from_fn(move || { - if rest.is_empty() { - return None; +pub fn parse_spec_and_escape( + fmt: &[u8], +) -> impl Iterator, FormatError>> + '_ { + let mut current = fmt; + std::iter::from_fn(move || match current { + [] => return None, + [b'%', b'%', rest @ ..] => { + current = rest; + Some(Ok(FormatItem::Char(EscapedChar::Char(b'%')))) + } + [b'%', rest @ ..] => { + current = rest; + let spec = match Spec::parse(&mut current) { + Some(spec) => spec, + None => return Some(Err(FormatError::SpecError)), + }; + Some(Ok(FormatItem::Spec(spec))) } + [b'\\', rest @ ..] => { + current = rest; + Some(Ok(FormatItem::Char(parse_escape_code(&mut current)))) + } + [c, rest @ ..] => { + current = rest; + Some(Ok(FormatItem::Char(EscapedChar::Char(*c)))) + } + }) +} - match rest.iter().position(|c| *c == b'%') { - None => { - let final_text = rest; - rest = &[]; - Some(Ok(FormatItem::Text(final_text.into()))) - } - Some(0) => { - // Handle the spec - rest = &rest[1..]; - match rest.get(0) { - None => Some(Ok(FormatItem::Char(b'%'))), - Some(b'%') => { - rest = &rest[1..]; - Some(Ok(FormatItem::Char(b'%'))) - } - Some(_) => { - let spec = match Spec::parse(&mut rest) { - Some(spec) => spec, - None => return Some(Err(dbg!(FormatError::SpecError))), - }; - Some(Ok(FormatItem::Spec(spec))) - } - } - } - Some(i) => { - // The `after` slice includes the % so it will be handled correctly - // in the next iteration. - let (before, after) = rest.split_at(i); - rest = after; - return Some(Ok(FormatItem::Text(before.into()))); - } +fn parse_spec_only(fmt: &[u8]) -> impl Iterator, FormatError>> + '_ { + let mut current = fmt; + std::iter::from_fn(move || match current { + [] => return None, + [b'%', b'%', rest @ ..] => { + current = rest; + Some(Ok(FormatItem::Char(b'%'))) + } + [b'%', rest @ ..] => { + current = rest; + let spec = match Spec::parse(&mut current) { + Some(spec) => spec, + None => return Some(Err(FormatError::SpecError)), + }; + Some(Ok(FormatItem::Spec(spec))) + } + [c, rest @ ..] => { + current = rest; + Some(Ok(FormatItem::Char(*c))) + } + }) +} + +fn parse_escape_only(fmt: &[u8]) -> impl Iterator> + '_ { + let mut current = fmt; + std::iter::from_fn(move || match current { + [] => return None, + [b'\\', rest @ ..] => { + current = rest; + Some(Ok(parse_escape_code(&mut current))) + } + [c, rest @ ..] => { + current = rest; + Some(Ok(EscapedChar::Char(*c))) } }) } @@ -144,7 +196,7 @@ fn parse_iter(fmt: &[u8]) -> impl Iterator( @@ -160,7 +212,7 @@ fn printf_writer<'a>( args: impl IntoIterator, ) -> Result<(), FormatError> { let mut args = args.into_iter(); - for item in parse_iter(format_string.as_ref()) { + for item in parse_spec_only(format_string.as_ref()) { item?.write(&mut writer, &mut args)?; } Ok(()) @@ -191,10 +243,10 @@ pub fn sprintf<'a>( } /// A parsed format for a single float value -/// +/// /// This is used by `seq`. It can be constructed with [`FloatFormat::parse`] /// and can write a value with [`FloatFormat::fmt`]. -/// +/// /// It can only accept a single specification without any asterisk parameters. /// If it does get more specifications, it will return an error. pub struct Format { @@ -205,7 +257,7 @@ pub struct Format { impl Format { pub fn parse(format_string: impl AsRef<[u8]>) -> Result { - let mut iter = parse_iter(format_string.as_ref()); + let mut iter = parse_spec_only(format_string.as_ref()); let mut prefix = Vec::new(); let mut spec = None; @@ -215,7 +267,6 @@ impl Format { spec = Some(s); break; } - FormatItem::Text(t) => prefix.extend_from_slice(&t), FormatItem::Char(c) => prefix.push(c), } } @@ -230,9 +281,8 @@ impl Format { for item in &mut iter { match item? { FormatItem::Spec(_) => { - return Err(dbg!(FormatError::SpecError)); + return Err(FormatError::SpecError); } - FormatItem::Text(t) => suffix.extend_from_slice(&t), FormatItem::Char(c) => suffix.push(c), } } diff --git a/src/uucore/src/lib/features/format/num_format.rs b/src/uucore/src/lib/features/format/num_format.rs index 046249a13d2..339b522091d 100644 --- a/src/uucore/src/lib/features/format/num_format.rs +++ b/src/uucore/src/lib/features/format/num_format.rs @@ -93,7 +93,7 @@ impl Formatter for SignedInt { alignment, } = s else { - return Err(dbg!(FormatError::SpecError)); + return Err(FormatError::SpecError); }; let width = match width { @@ -152,7 +152,7 @@ impl Formatter for UnsignedInt { alignment, } = s else { - return Err(dbg!(FormatError::SpecError)); + return Err(FormatError::SpecError); }; let width = match width { @@ -241,19 +241,19 @@ impl Formatter for Float { precision, } = s else { - return Err(dbg!(FormatError::SpecError)); + return Err(FormatError::SpecError); }; let width = match width { Some(CanAsterisk::Fixed(x)) => x, None => 0, - Some(CanAsterisk::Asterisk) => return Err(dbg!(FormatError::SpecError)), + Some(CanAsterisk::Asterisk) => return Err(FormatError::SpecError), }; let precision = match precision { Some(CanAsterisk::Fixed(x)) => x, None => 0, - Some(CanAsterisk::Asterisk) => return Err(dbg!(FormatError::SpecError)), + Some(CanAsterisk::Asterisk) => return Err(FormatError::SpecError), }; Ok(Self { diff --git a/src/uucore/src/lib/features/format/spec.rs b/src/uucore/src/lib/features/format/spec.rs index abc9b7a875a..258005bb5e6 100644 --- a/src/uucore/src/lib/features/format/spec.rs +++ b/src/uucore/src/lib/features/format/spec.rs @@ -212,10 +212,7 @@ impl Spec { (false, false) => PositiveSign::None, }, }, - x => { - dbg!("{:b}", x); - return dbg!(None) - }, + _ => return None, }) } @@ -228,16 +225,16 @@ impl Spec { &Spec::Char { width, align_left } => { let width = resolve_asterisk(width, &mut args)?.unwrap_or(0); let arg = next_arg(&mut args)?; - match arg { - FormatArgument::Char(c) => write_padded(writer, c, width, false, align_left), + match arg.get_char() { + Some(c) => write_padded(writer, c, width, false, align_left), _ => Err(FormatError::InvalidArgument(arg.clone())), } } &Spec::String { width, align_left } => { let width = resolve_asterisk(width, &mut args)?.unwrap_or(0); let arg = next_arg(&mut args)?; - match arg { - FormatArgument::String(s) => write_padded(writer, s, width, false, align_left), + match arg.get_str() { + Some(s) => write_padded(writer, s, width, false, align_left), _ => Err(FormatError::InvalidArgument(arg.clone())), } } @@ -249,7 +246,7 @@ impl Spec { let width = resolve_asterisk(width, &mut args)?.unwrap_or(0); let arg = next_arg(&mut args)?; - let FormatArgument::SignedInt(i) = arg else { + let Some(i) = arg.get_i64() else { return Err(FormatError::InvalidArgument(arg.clone())); }; @@ -258,7 +255,7 @@ impl Spec { positive_sign, alignment, } - .fmt(writer, *i) + .fmt(writer, i) .map_err(FormatError::IoError) } &Spec::UnsignedInt { @@ -269,7 +266,7 @@ impl Spec { let width = resolve_asterisk(width, &mut args)?.unwrap_or(0); let arg = next_arg(args)?; - let FormatArgument::UnsignedInt(i) = arg else { + let Some(i) = arg.get_u64() else { return Err(FormatError::InvalidArgument(arg.clone())); }; @@ -278,7 +275,7 @@ impl Spec { width, alignment, } - .fmt(writer, *i) + .fmt(writer, i) .map_err(FormatError::IoError) } &Spec::Float { @@ -294,7 +291,7 @@ impl Spec { let precision = resolve_asterisk(precision, &mut args)?.unwrap_or(6); let arg = next_arg(args)?; - let FormatArgument::Float(f) = arg else { + let Some(f) = arg.get_f64() else { return Err(FormatError::InvalidArgument(arg.clone())); }; @@ -307,7 +304,7 @@ impl Spec { alignment, precision, } - .fmt(writer, *f) + .fmt(writer, f) .map_err(FormatError::IoError) } } @@ -322,8 +319,8 @@ fn resolve_asterisk<'a>( None => None, Some(CanAsterisk::Asterisk) => { let arg = next_arg(args)?; - match arg { - FormatArgument::UnsignedInt(u) => match usize::try_from(*u) { + match arg.get_u64() { + Some(u) => match usize::try_from(u) { Ok(u) => Some(u), Err(_) => return Err(FormatError::InvalidArgument(arg.clone())), }, From a45ff8ca73060939983379f5d973ac3da4c75330 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Fri, 17 Nov 2023 14:39:39 +0100 Subject: [PATCH 0361/2851] printf: more flexible parsing of unparsed arguments --- .../src/lib/features/format/argument.rs | 40 ++++++++++++++++--- 1 file changed, 34 insertions(+), 6 deletions(-) diff --git a/src/uucore/src/lib/features/format/argument.rs b/src/uucore/src/lib/features/format/argument.rs index 007f519c2a4..644546c38b7 100644 --- a/src/uucore/src/lib/features/format/argument.rs +++ b/src/uucore/src/lib/features/format/argument.rs @@ -30,23 +30,51 @@ impl FormatArgument { pub fn get_u64(&self) -> Option { match self { Self::UnsignedInt(n) => Some(*n), - Self::Unparsed(s) => s.parse().ok(), + Self::Unparsed(s) => { + if let Some(s) = s.strip_prefix("0x") { + u64::from_str_radix(s, 16).ok() + } else if let Some(s) = s.strip_prefix("0") { + u64::from_str_radix(s, 8).ok() + } else if let Some(s) = s.strip_prefix('\'') { + Some(s.chars().next()? as u64) + } else { + s.parse().ok() + } + } _ => None, } } - + pub fn get_i64(&self) -> Option { match self { Self::SignedInt(n) => Some(*n), - Self::Unparsed(s) => s.parse().ok(), + Self::Unparsed(s) => { + if let Some(s) = s.strip_prefix("0x") { + i64::from_str_radix(s, 16).ok() + } else if let Some(s) = s.strip_prefix("0") { + i64::from_str_radix(s, 8).ok() + } else if let Some(s) = s.strip_prefix('\'') { + Some(s.chars().next()? as i64) + } else { + s.parse().ok() + } + } _ => None, } } - + pub fn get_f64(&self) -> Option { match self { Self::Float(n) => Some(*n), - Self::Unparsed(s) => s.parse().ok(), + Self::Unparsed(s) => { + if s.starts_with("0x") || s.starts_with("-0x") { + unimplemented!("Hexadecimal floats are unimplemented!") + } else if let Some(s) = s.strip_prefix('\'') { + Some(s.chars().next()? as u64 as f64) + } else { + s.parse().ok() + } + } _ => None, } } @@ -57,4 +85,4 @@ impl FormatArgument { _ => None, } } -} \ No newline at end of file +} From cd0c24af07d1412a746c5dbfe8a3df0a8cb56191 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Fri, 17 Nov 2023 14:41:14 +0100 Subject: [PATCH 0362/2851] printf: implement %b --- src/uucore/src/lib/features/format/mod.rs | 6 +-- src/uucore/src/lib/features/format/spec.rs | 43 +++++++++++++++++++--- 2 files changed, 40 insertions(+), 9 deletions(-) diff --git a/src/uucore/src/lib/features/format/mod.rs b/src/uucore/src/lib/features/format/mod.rs index 8fa8d0717e1..7417d48fad5 100644 --- a/src/uucore/src/lib/features/format/mod.rs +++ b/src/uucore/src/lib/features/format/mod.rs @@ -169,17 +169,17 @@ fn parse_spec_only(fmt: &[u8]) -> impl Iterator, Fo }) } -fn parse_escape_only(fmt: &[u8]) -> impl Iterator> + '_ { +fn parse_escape_only(fmt: &[u8]) -> impl Iterator + '_ { let mut current = fmt; std::iter::from_fn(move || match current { [] => return None, [b'\\', rest @ ..] => { current = rest; - Some(Ok(parse_escape_code(&mut current))) + Some(parse_escape_code(&mut current)) } [c, rest @ ..] => { current = rest; - Some(Ok(EscapedChar::Char(*c))) + Some(EscapedChar::Char(*c)) } }) } diff --git a/src/uucore/src/lib/features/format/spec.rs b/src/uucore/src/lib/features/format/spec.rs index 258005bb5e6..dc55bc653e6 100644 --- a/src/uucore/src/lib/features/format/spec.rs +++ b/src/uucore/src/lib/features/format/spec.rs @@ -5,9 +5,9 @@ use super::{ self, Case, FloatVariant, ForceDecimal, Formatter, NumberAlignment, PositiveSign, Prefix, UnsignedIntVariant, }, - FormatArgument, FormatError, + parse_escape_only, FormatArgument, FormatChar, FormatError, }; -use std::{fmt::Display, io::Write}; +use std::{fmt::Display, io::Write, ops::ControlFlow}; #[derive(Debug)] pub enum Spec { @@ -17,6 +17,7 @@ pub enum Spec { }, String { width: Option>, + parse_escape: bool, align_left: bool, }, SignedInt { @@ -145,6 +146,12 @@ impl Spec { }, b's' => Spec::String { width, + parse_escape: false, + align_left: minus, + }, + b'b' => Spec::String { + width, + parse_escape: true, align_left: minus, }, b'd' | b'i' => Spec::SignedInt { @@ -230,12 +237,36 @@ impl Spec { _ => Err(FormatError::InvalidArgument(arg.clone())), } } - &Spec::String { width, align_left } => { + &Spec::String { + width, + parse_escape, + align_left, + } => { let width = resolve_asterisk(width, &mut args)?.unwrap_or(0); let arg = next_arg(&mut args)?; - match arg.get_str() { - Some(s) => write_padded(writer, s, width, false, align_left), - _ => Err(FormatError::InvalidArgument(arg.clone())), + let Some(s) = arg.get_str() else { + return Err(FormatError::InvalidArgument(arg.clone())); + }; + if parse_escape { + let mut parsed = Vec::new(); + for c in parse_escape_only(s.as_bytes()) { + match c.write(&mut parsed)? { + ControlFlow::Continue(()) => {} + ControlFlow::Break(()) => { + // TODO: This should break the _entire execution_ of printf + break; + } + }; + } + write_padded( + writer, + std::str::from_utf8(&parsed).expect("TODO: Accept invalid utf8"), + width, + false, + align_left, + ) + } else { + write_padded(writer, s, width, false, align_left) } } &Spec::SignedInt { From f83e0d1b04a55feaf7a4dbb810c9dda7a007dc40 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Fri, 17 Nov 2023 14:41:42 +0100 Subject: [PATCH 0363/2851] printf: accept multiple length parameters --- src/uucore/src/lib/features/format/spec.rs | 63 +++++++++++++--------- 1 file changed, 37 insertions(+), 26 deletions(-) diff --git a/src/uucore/src/lib/features/format/spec.rs b/src/uucore/src/lib/features/format/spec.rs index dc55bc653e6..0f48cdafe9f 100644 --- a/src/uucore/src/lib/features/format/spec.rs +++ b/src/uucore/src/lib/features/format/spec.rs @@ -107,34 +107,45 @@ impl Spec { None }; - let length = rest.get(0).and_then(|c| { - Some(match c { - b'h' => { - if let Some(b'h') = rest.get(1) { - *rest = &rest[1..]; - Length::Char - } else { - Length::Short + // Parse 0..N length options, keep the last one + // Even though it is just ignored. We might want to use it later and we + // should parse those characters. + // + // TODO: This needs to be configurable: `seq` accepts only one length + // param + let mut _length = None; + loop { + let new_length = rest.get(0).and_then(|c| { + Some(match c { + b'h' => { + if let Some(b'h') = rest.get(1) { + *rest = &rest[1..]; + Length::Char + } else { + Length::Short + } } - } - b'l' => { - if let Some(b'l') = rest.get(1) { - *rest = &rest[1..]; - Length::Long - } else { - Length::LongLong + b'l' => { + if let Some(b'l') = rest.get(1) { + *rest = &rest[1..]; + Length::Long + } else { + Length::LongLong + } } - } - b'j' => Length::IntMaxT, - b'z' => Length::SizeT, - b't' => Length::PtfDiffT, - b'L' => Length::LongDouble, - _ => return None, - }) - }); - - if length.is_some() { - *rest = &rest[1..]; + b'j' => Length::IntMaxT, + b'z' => Length::SizeT, + b't' => Length::PtfDiffT, + b'L' => Length::LongDouble, + _ => return None, + }) + }); + if new_length.is_some() { + *rest = &rest[1..]; + _length = new_length; + } else { + break; + } } let type_spec = rest.get(0)?; From f3da0817a57d740a527d07b5a74368ed41fd3d08 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Fri, 17 Nov 2023 14:42:52 +0100 Subject: [PATCH 0364/2851] printf: support precision for integers --- .../src/lib/features/format/num_format.rs | 32 ++++++++++++++++--- src/uucore/src/lib/features/format/spec.rs | 10 ++++++ 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/src/uucore/src/lib/features/format/num_format.rs b/src/uucore/src/lib/features/format/num_format.rs index 339b522091d..fab81c456fc 100644 --- a/src/uucore/src/lib/features/format/num_format.rs +++ b/src/uucore/src/lib/features/format/num_format.rs @@ -63,6 +63,7 @@ pub enum NumberAlignment { pub struct SignedInt { pub width: usize, + pub precision: usize, pub positive_sign: PositiveSign, pub alignment: NumberAlignment, } @@ -79,16 +80,19 @@ impl Formatter for SignedInt { }?; } + let s = format!("{:0width$}", x, width = self.precision); + match self.alignment { - NumberAlignment::Left => write!(writer, "{x: write!(writer, "{x:>width$}", width = self.width), - NumberAlignment::RightZero => write!(writer, "{x:0>width$}", width = self.width), + NumberAlignment::Left => write!(writer, "{s: write!(writer, "{s:>width$}", width = self.width), + NumberAlignment::RightZero => write!(writer, "{s:0>width$}", width = self.width), } } fn try_from_spec(s: Spec) -> Result { let Spec::SignedInt { width, + precision, positive_sign, alignment, } = s @@ -102,8 +106,15 @@ impl Formatter for SignedInt { Some(CanAsterisk::Asterisk) => return Err(FormatError::SpecError), }; + let precision = match precision { + Some(CanAsterisk::Fixed(x)) => x, + None => 0, + Some(CanAsterisk::Asterisk) => return Err(FormatError::SpecError), + }; + Ok(Self { width, + precision, positive_sign, alignment, }) @@ -113,6 +124,7 @@ impl Formatter for SignedInt { pub struct UnsignedInt { pub variant: UnsignedIntVariant, pub width: usize, + pub precision: usize, pub alignment: NumberAlignment, } @@ -120,7 +132,7 @@ impl Formatter for UnsignedInt { type Input = u64; fn fmt(&self, mut writer: impl Write, x: Self::Input) -> std::io::Result<()> { - let s = match self.variant { + let mut s = match self.variant { UnsignedIntVariant::Decimal => format!("{x}"), UnsignedIntVariant::Octal(Prefix::No) => format!("{x:o}"), UnsignedIntVariant::Octal(Prefix::Yes) => format!("{x:#o}"), @@ -138,6 +150,10 @@ impl Formatter for UnsignedInt { } }; + if self.precision > s.len() { + s = format!("{:0width$}", s, width = self.precision) + } + match self.alignment { NumberAlignment::Left => write!(writer, "{s: write!(writer, "{s:>width$}", width = self.width), @@ -149,6 +165,7 @@ impl Formatter for UnsignedInt { let Spec::UnsignedInt { variant, width, + precision, alignment, } = s else { @@ -161,8 +178,15 @@ impl Formatter for UnsignedInt { Some(CanAsterisk::Asterisk) => return Err(FormatError::SpecError), }; + let precision = match precision { + Some(CanAsterisk::Fixed(x)) => x, + None => 0, + Some(CanAsterisk::Asterisk) => return Err(FormatError::SpecError), + }; + Ok(Self { width, + precision, variant, alignment, }) diff --git a/src/uucore/src/lib/features/format/spec.rs b/src/uucore/src/lib/features/format/spec.rs index 0f48cdafe9f..06f0ca1d6e7 100644 --- a/src/uucore/src/lib/features/format/spec.rs +++ b/src/uucore/src/lib/features/format/spec.rs @@ -22,12 +22,14 @@ pub enum Spec { }, SignedInt { width: Option>, + precision: Option>, positive_sign: PositiveSign, alignment: NumberAlignment, }, UnsignedInt { variant: UnsignedIntVariant, width: Option>, + precision: Option>, alignment: NumberAlignment, }, Float { @@ -167,6 +169,7 @@ impl Spec { }, b'd' | b'i' => Spec::SignedInt { width, + precision, alignment: match (minus, zero) { (true, _) => NumberAlignment::Left, (false, true) => NumberAlignment::RightZero, @@ -197,6 +200,7 @@ impl Spec { }; Spec::UnsignedInt { variant, + precision, width, alignment, } @@ -282,10 +286,12 @@ impl Spec { } &Spec::SignedInt { width, + precision, positive_sign, alignment, } => { let width = resolve_asterisk(width, &mut args)?.unwrap_or(0); + let precision = resolve_asterisk(precision, &mut args)?.unwrap_or(0); let arg = next_arg(&mut args)?; let Some(i) = arg.get_i64() else { @@ -294,6 +300,7 @@ impl Spec { num_format::SignedInt { width, + precision, positive_sign, alignment, } @@ -303,9 +310,11 @@ impl Spec { &Spec::UnsignedInt { variant, width, + precision, alignment, } => { let width = resolve_asterisk(width, &mut args)?.unwrap_or(0); + let precision = resolve_asterisk(precision, &mut args)?.unwrap_or(0); let arg = next_arg(args)?; let Some(i) = arg.get_u64() else { @@ -314,6 +323,7 @@ impl Spec { num_format::UnsignedInt { variant, + precision, width, alignment, } From 76eca8d9996cd96751a1ab8a0e7577bff967fd6a Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Fri, 17 Nov 2023 14:43:25 +0100 Subject: [PATCH 0365/2851] uucore/format: fix doctests --- src/uucore/src/lib/features/format/mod.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/uucore/src/lib/features/format/mod.rs b/src/uucore/src/lib/features/format/mod.rs index 7417d48fad5..47e6fa4cc0b 100644 --- a/src/uucore/src/lib/features/format/mod.rs +++ b/src/uucore/src/lib/features/format/mod.rs @@ -194,9 +194,9 @@ fn parse_escape_only(fmt: &[u8]) -> impl Iterator + '_ { /// # Examples /// /// ```rust -/// use uucore::format::printf; +/// use uucore::format::{printf, FormatArgument}; /// -/// printf("hello %s", &[FormatArgument::String("world")]).unwrap(); +/// printf("hello %s", &[FormatArgument::String("world".into())]).unwrap(); /// // prints "hello world" /// ``` pub fn printf<'a>( @@ -228,10 +228,11 @@ fn printf_writer<'a>( /// # Examples /// /// ```rust -/// use uucore::format::sprintf; +/// use uucore::format::{sprintf, FormatArgument}; /// -/// let s = sprintf("hello %s", &["world".to_string()]).unwrap(); -/// assert_eq!(s, "hello world".to_string()); +/// let s = sprintf("hello %s", &[FormatArgument::String("world".into())]).unwrap(); +/// let s = std::str::from_utf8(&s).unwrap(); +/// assert_eq!(s, "hello world"); /// ``` pub fn sprintf<'a>( format_string: impl AsRef<[u8]>, From 4aafb3f88ba2e3113df29ab264fe507ab78fdfb1 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Fri, 17 Nov 2023 14:46:00 +0100 Subject: [PATCH 0366/2851] printf: exit correctly on \c --- src/uu/printf/src/printf.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/uu/printf/src/printf.rs b/src/uu/printf/src/printf.rs index 00d03816e4c..663411b8952 100644 --- a/src/uu/printf/src/printf.rs +++ b/src/uu/printf/src/printf.rs @@ -43,7 +43,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { for item in parse_spec_and_escape(format_string.as_ref()) { match item?.write(stdout(), &mut args)? { ControlFlow::Continue(()) => {} - ControlFlow::Break(()) => break, + ControlFlow::Break(()) => return Ok(()), }; } @@ -51,7 +51,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { for item in parse_spec_and_escape(format_string.as_ref()) { match item?.write(stdout(), &mut args)? { ControlFlow::Continue(()) => {} - ControlFlow::Break(()) => break, + ControlFlow::Break(()) => return Ok(()), }; } } From 955640aac8a9b1b40932bf6f282ea48ce30398b8 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Fri, 17 Nov 2023 14:46:38 +0100 Subject: [PATCH 0367/2851] printf: fix and test float formatting --- .../src/lib/features/format/num_format.rs | 205 +++++++++++++++++- tests/by-util/test_printf.rs | 2 +- 2 files changed, 198 insertions(+), 9 deletions(-) diff --git a/src/uucore/src/lib/features/format/num_format.rs b/src/uucore/src/lib/features/format/num_format.rs index fab81c456fc..49edecce085 100644 --- a/src/uucore/src/lib/features/format/num_format.rs +++ b/src/uucore/src/lib/features/format/num_format.rs @@ -329,8 +329,24 @@ fn format_float_scientific( return format_float_nonfinite(f, case); } - let exponent: i32 = f.log10().floor() as i32; - let normalized = f / 10.0_f64.powi(exponent); + if f == 0.0 { + return if force_decimal == ForceDecimal::Yes && precision == 0 { + "0.e+00".into() + } else { + format!("{:.*}e+00", precision, 0.0) + }; + } + + + let mut exponent: i32 = f.log10().floor() as i32; + let mut normalized = f / 10.0_f64.powi(exponent); + + // If the normalized value will be rounded to a value greater than 10 + // we need to correct. + if (normalized * 10_f64.powi(precision as i32)).round() / 10_f64.powi(precision as i32) >= 10.0 { + normalized /= 10.0; + exponent += 1; + } let additional_dot = if precision == 0 && ForceDecimal::Yes == force_decimal { "." @@ -349,20 +365,89 @@ fn format_float_scientific( ) } -// TODO: This could be optimized. It's not terribly important though. fn format_float_shortest( f: f64, precision: usize, case: Case, force_decimal: ForceDecimal, ) -> String { - let a = format_float_decimal(f, precision, case, force_decimal); - let b = format_float_scientific(f, precision, case, force_decimal); + // If the float is NaN, -Nan, Inf or -Inf, format like any other float + if !f.is_finite() { + return format_float_nonfinite(f, case); + } - if a.len() > b.len() { - b + // Precision here is about how many digits should be displayed + // instead of how many digits for the fractional part, this means that if + // we pass this to rust's format string, it's always gonna be one less. + let precision = precision.saturating_sub(1); + + if f == 0.0 { + return match (force_decimal, precision) { + (ForceDecimal::Yes, 0) => "0.".into(), + (ForceDecimal::Yes, _) => { + format!("{:.*}", precision, 0.0) + } + (ForceDecimal::No, _) => "0".into(), + }; + } + + let mut exponent = f.log10().floor() as i32; + if f != 0.0 && exponent <= -4 || exponent > precision as i32 { + // Scientific-ish notation (with a few differences) + let mut normalized = f / 10.0_f64.powi(exponent); + + // If the normalized value will be rounded to a value greater than 10 + // we need to correct. + if (normalized * 10_f64.powi(precision as i32)).round() / 10_f64.powi(precision as i32) >= 10.0 { + normalized /= 10.0; + exponent += 1; + } + + let additional_dot = if precision == 0 && ForceDecimal::Yes == force_decimal { + "." + } else { + "" + }; + + let mut normalized = format!("{normalized:.*}", precision); + + if force_decimal == ForceDecimal::No { + while normalized.ends_with('0') { + normalized.pop(); + } + if normalized.ends_with('.') { + normalized.pop(); + } + } + + let exp_char = match case { + Case::Lowercase => 'e', + Case::Uppercase => 'E', + }; + + format!("{normalized}{additional_dot}{exp_char}{exponent:+03}") } else { - a + // Decimal-ish notation with a few differences: + // - The precision works differently and specifies the total number + // of digits instead of the digits in the fractional part. + // - If we don't force the decimal, '0' and `.` are trimmed. + let decimal_places = (precision as i32).saturating_sub(exponent) as usize; + let mut formatted = if decimal_places == 0 && force_decimal == ForceDecimal::Yes { + format!("{f:.0}.") + } else { + format!("{f:.*}", decimal_places) + }; + + if force_decimal == ForceDecimal::No { + while formatted.ends_with('0') { + formatted.pop(); + } + if formatted.ends_with('.') { + formatted.pop(); + } + } + + formatted } } @@ -398,3 +483,107 @@ fn format_float_hexadecimal( return s; } + +#[cfg(test)] +mod test { + use crate::format::num_format::{Case, ForceDecimal}; + + #[test] + fn decimal_float() { + use super::format_float_decimal; + let f = |x| format_float_decimal(x, 6, Case::Lowercase, ForceDecimal::No); + assert_eq!(f(0.0), "0.000000"); + assert_eq!(f(1.0), "1.000000"); + assert_eq!(f(100.0), "100.000000"); + assert_eq!(f(123456.789), "123456.789000"); + assert_eq!(f(12.3456789), "12.345679"); + assert_eq!(f(1000000.0), "1000000.000000"); + assert_eq!(f(99999999.0), "99999999.000000"); + assert_eq!(f(1.9999995), "1.999999"); + assert_eq!(f(1.9999996), "2.000000"); + } + + #[test] + fn scientific_float() { + use super::format_float_scientific; + let f = |x| format_float_scientific(x, 6, Case::Lowercase, ForceDecimal::No); + assert_eq!(f(0.0), "0.000000e+00"); + assert_eq!(f(1.0), "1.000000e+00"); + assert_eq!(f(100.0), "1.000000e+02"); + assert_eq!(f(123456.789), "1.234568e+05"); + assert_eq!(f(12.3456789), "1.234568e+01"); + assert_eq!(f(1000000.0), "1.000000e+06"); + assert_eq!(f(99999999.0), "1.000000e+08"); + } + + #[test] + fn scientific_float_zero_precision() { + use super::format_float_scientific; + + let f = |x| format_float_scientific(x, 0, Case::Lowercase, ForceDecimal::No); + assert_eq!(f(0.0), "0e+00"); + assert_eq!(f(1.0), "1e+00"); + assert_eq!(f(100.0), "1e+02"); + assert_eq!(f(123456.789), "1e+05"); + assert_eq!(f(12.3456789), "1e+01"); + assert_eq!(f(1000000.0), "1e+06"); + assert_eq!(f(99999999.0), "1e+08"); + + let f = |x| format_float_scientific(x, 0, Case::Lowercase, ForceDecimal::Yes); + assert_eq!(f(0.0), "0.e+00"); + assert_eq!(f(1.0), "1.e+00"); + assert_eq!(f(100.0), "1.e+02"); + assert_eq!(f(123456.789), "1.e+05"); + assert_eq!(f(12.3456789), "1.e+01"); + assert_eq!(f(1000000.0), "1.e+06"); + assert_eq!(f(99999999.0), "1.e+08"); + } + + #[test] + fn shortest_float() { + use super::format_float_shortest; + let f = |x| format_float_shortest(x, 6, Case::Lowercase, ForceDecimal::No); + assert_eq!(f(0.0), "0"); + assert_eq!(f(1.0), "1"); + assert_eq!(f(100.0), "100"); + assert_eq!(f(123456.789), "123457"); + assert_eq!(f(12.3456789), "12.3457"); + assert_eq!(f(1000000.0), "1e+06"); + assert_eq!(f(99999999.0), "1e+08"); + } + + #[test] + fn shortest_float_force_decimal() { + use super::format_float_shortest; + let f = |x| format_float_shortest(x, 6, Case::Lowercase, ForceDecimal::Yes); + assert_eq!(f(0.0), "0.00000"); + assert_eq!(f(1.0), "1.00000"); + assert_eq!(f(100.0), "100.000"); + assert_eq!(f(123456.789), "123457."); + assert_eq!(f(12.3456789), "12.3457"); + assert_eq!(f(1000000.0), "1.00000e+06"); + assert_eq!(f(99999999.0), "1.00000e+08"); + } + + #[test] + fn shortest_float_force_decimal_zero_precision() { + use super::format_float_shortest; + let f = |x| format_float_shortest(x, 0, Case::Lowercase, ForceDecimal::No); + assert_eq!(f(0.0), "0"); + assert_eq!(f(1.0), "1"); + assert_eq!(f(100.0), "1e+02"); + assert_eq!(f(123456.789), "1e+05"); + assert_eq!(f(12.3456789), "1e+01"); + assert_eq!(f(1000000.0), "1e+06"); + assert_eq!(f(99999999.0), "1e+08"); + + let f = |x| format_float_shortest(x, 0, Case::Lowercase, ForceDecimal::Yes); + assert_eq!(f(0.0), "0."); + assert_eq!(f(1.0), "1."); + assert_eq!(f(100.0), "1.e+02"); + assert_eq!(f(123456.789), "1.e+05"); + assert_eq!(f(12.3456789), "1.e+01"); + assert_eq!(f(1000000.0), "1.e+06"); + assert_eq!(f(99999999.0), "1.e+08"); + } +} diff --git a/tests/by-util/test_printf.rs b/tests/by-util/test_printf.rs index d7ba5679ecf..5a74aa724e4 100644 --- a/tests/by-util/test_printf.rs +++ b/tests/by-util/test_printf.rs @@ -251,7 +251,7 @@ fn sub_num_float_e_no_round() { #[test] fn sub_num_float_round() { new_ucmd!() - .args(&["two is %f", "1.9999995"]) + .args(&["two is %f", "1.9999996"]) .succeeds() .stdout_only("two is 2.000000"); } From fef84f72030fdd13065bd6735b1b01a40a36e52f Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Fri, 17 Nov 2023 14:47:08 +0100 Subject: [PATCH 0368/2851] printf: add emoji character test --- tests/by-util/test_printf.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/by-util/test_printf.rs b/tests/by-util/test_printf.rs index 5a74aa724e4..7573b5fb983 100644 --- a/tests/by-util/test_printf.rs +++ b/tests/by-util/test_printf.rs @@ -190,6 +190,11 @@ fn sub_num_int_char_const_in() { .args(&["ninety seven is %i", "'a"]) .succeeds() .stdout_only("ninety seven is 97"); + + new_ucmd!() + .args(&["emoji is %i", "'🙃"]) + .succeeds() + .stdout_only("emoji is 128579"); } #[test] From ce18e0ab9702c73d778b6b4bb9218926562c83fa Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Fri, 17 Nov 2023 14:47:41 +0100 Subject: [PATCH 0369/2851] printf: ignore hexadecimal floats test This can be un-ignored when it is implemented --- tests/by-util/test_printf.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/by-util/test_printf.rs b/tests/by-util/test_printf.rs index 7573b5fb983..436bc498b7c 100644 --- a/tests/by-util/test_printf.rs +++ b/tests/by-util/test_printf.rs @@ -376,6 +376,7 @@ fn sub_float_dec_places() { } #[test] +#[ignore = "hexadecimal floats are unimplemented"] fn sub_float_hex_in() { new_ucmd!() .args(&["%f", "0xF1.1F"]) From 5f2374b33960f42fd1cb575ee4801ef50f342cf8 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Fri, 17 Nov 2023 14:57:09 +0100 Subject: [PATCH 0370/2851] printf: fix negative hex argument parsing --- src/uucore/src/lib/features/format/argument.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/uucore/src/lib/features/format/argument.rs b/src/uucore/src/lib/features/format/argument.rs index 644546c38b7..120b59aa4b5 100644 --- a/src/uucore/src/lib/features/format/argument.rs +++ b/src/uucore/src/lib/features/format/argument.rs @@ -49,9 +49,13 @@ impl FormatArgument { match self { Self::SignedInt(n) => Some(*n), Self::Unparsed(s) => { - if let Some(s) = s.strip_prefix("0x") { - i64::from_str_radix(s, 16).ok() - } else if let Some(s) = s.strip_prefix("0") { + // For hex, we parse `u64` because we do not allow another + // minus sign. We might need to do more precise parsing here. + if let Some(s) = s.strip_prefix("-0x") { + Some(- (u64::from_str_radix(s, 16).ok()? as i64)) + } else if let Some(s) = s.strip_prefix("0x") { + Some(u64::from_str_radix(s, 16).ok()? as i64) + } else if s.starts_with("-0") || s.starts_with('0') { i64::from_str_radix(s, 8).ok() } else if let Some(s) = s.strip_prefix('\'') { Some(s.chars().next()? as i64) From eb00c195c6c9e014c89d53b7d0e6feade507da8d Mon Sep 17 00:00:00 2001 From: Yury Zhytkou <54360928+zhitkoff@users.noreply.github.com> Date: Fri, 17 Nov 2023 11:19:10 -0500 Subject: [PATCH 0371/2851] split: pass GNU tests/b-chunk.sh (#5475) --------- Co-authored-by: Terts Diepraam Co-authored-by: Daniel Hofstetter Co-authored-by: Brandon Elam Barker Co-authored-by: Kostiantyn Hryshchuk Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- src/uu/split/src/split.rs | 669 +++++++++++++++++++----------------- tests/by-util/test_split.rs | 130 ++++--- 2 files changed, 435 insertions(+), 364 deletions(-) diff --git a/src/uu/split/src/split.rs b/src/uu/split/src/split.rs index 17a783d72f2..592e4eedde9 100644 --- a/src/uu/split/src/split.rs +++ b/src/uu/split/src/split.rs @@ -18,11 +18,12 @@ use std::ffi::OsString; use std::fmt; use std::fs::{metadata, File}; use std::io; -use std::io::{stdin, BufRead, BufReader, BufWriter, ErrorKind, Read, Write}; +use std::io::{stdin, BufRead, BufReader, BufWriter, ErrorKind, Read, Seek, SeekFrom, Write}; use std::path::Path; use std::u64; use uucore::display::Quotable; use uucore::error::{FromIo, UIoError, UResult, USimpleError, UUsageError}; +use uucore::parse_size::parse_size_u64; use uucore::uio_error; use uucore::{format_usage, help_about, help_section, help_usage}; @@ -40,11 +41,20 @@ static OPT_HEX_SUFFIXES_SHORT: &str = "-x"; static OPT_SUFFIX_LENGTH: &str = "suffix-length"; static OPT_VERBOSE: &str = "verbose"; static OPT_SEPARATOR: &str = "separator"; -//The ---io and ---io-blksize parameters are consumed and ignored. -//The parameter is included to make GNU coreutils tests pass. -static OPT_IO: &str = "-io"; -static OPT_IO_BLKSIZE: &str = "-io-blksize"; static OPT_ELIDE_EMPTY_FILES: &str = "elide-empty-files"; +static OPT_IO_BLKSIZE: &str = "-io-blksize"; +// Cap ---io-blksize value +// For 64bit systems the max value is the same as in GNU +// and is equivalent of `i32::MAX >> 20 << 20` operation. +// On 32bit systems however, even though it fits within `u32` and `i32`, +// it causes rust-lang `library/alloc/src/raw_vec.rs` to panic with 'capacity overflow' error. +// Could be due to how `std::io::BufReader` handles internal buffers. +// So we use much smaller value for those +static OPT_IO_BLKSIZE_MAX: usize = if usize::BITS >= 64 { + 2_146_435_072 +} else { + 1_000_000_000 +}; static ARG_INPUT: &str = "input"; static ARG_PREFIX: &str = "prefix"; @@ -311,7 +321,6 @@ pub fn uu_app() -> Command { .arg( Arg::new(OPT_NUMERIC_SUFFIXES) .long(OPT_NUMERIC_SUFFIXES) - .alias("numeric") .require_equals(true) .num_args(0..=1) .overrides_with_all([ @@ -338,7 +347,6 @@ pub fn uu_app() -> Command { .arg( Arg::new(OPT_HEX_SUFFIXES) .long(OPT_HEX_SUFFIXES) - .alias("hex") .require_equals(true) .num_args(0..=1) .overrides_with_all([ @@ -373,12 +381,6 @@ pub fn uu_app() -> Command { .action(ArgAction::Append) .help("use SEP instead of newline as the record separator; '\\0' (zero) specifies the NUL character"), ) - .arg( - Arg::new(OPT_IO) - .long("io") - .alias(OPT_IO) - .hide(true), - ) .arg( Arg::new(OPT_IO_BLKSIZE) .long("io-blksize") @@ -419,6 +421,7 @@ struct Settings { /// chunks. If this is `false`, then empty files will not be /// created. elide_empty_files: bool, + io_blksize: Option, } /// An error when parsing settings from command-line arguments. @@ -441,6 +444,9 @@ enum SettingsError { /// r/K/N FilterWithKthChunkNumber, + /// Invalid IO block size + InvalidIOBlockSize(String), + /// The `--filter` option is not supported on Windows. #[cfg(windows)] NotSupported, @@ -471,6 +477,7 @@ impl fmt::Display for SettingsError { Self::FilterWithKthChunkNumber => { write!(f, "--filter does not process a chunk extracted to stdout") } + Self::InvalidIOBlockSize(s) => write!(f, "invalid IO block size: {}", s.quote()), #[cfg(windows)] Self::NotSupported => write!( f, @@ -499,12 +506,29 @@ impl Settings { match first.as_str() { "\\0" => b'\0', s if s.as_bytes().len() == 1 => s.as_bytes()[0], - s => return Err(SettingsError::MultiCharacterSeparator(s.to_owned())), + s => return Err(SettingsError::MultiCharacterSeparator(s.to_string())), } } None => b'\n', }; + let io_blksize: Option = if let Some(s) = matches.get_one::(OPT_IO_BLKSIZE) { + match parse_size_u64(s) { + Ok(n) => { + let n: usize = n + .try_into() + .map_err(|_| SettingsError::InvalidIOBlockSize(s.to_string()))?; + if n > OPT_IO_BLKSIZE_MAX { + return Err(SettingsError::InvalidIOBlockSize(s.to_string())); + } + Some(n) + } + _ => return Err(SettingsError::InvalidIOBlockSize(s.to_string())), + } + } else { + None + }; + let result = Self { prefix: matches.get_one::(ARG_PREFIX).unwrap().clone(), suffix, @@ -514,6 +538,7 @@ impl Settings { verbose: matches.value_source(OPT_VERBOSE) == Some(ValueSource::CommandLine), separator, elide_empty_files: matches.get_flag(OPT_ELIDE_EMPTY_FILES), + io_blksize, }; #[cfg(windows)] @@ -591,6 +616,93 @@ fn custom_write_all( } } +/// Get the size of the input file in bytes +/// Used only for subset of `--number=CHUNKS` strategy, as there is a need +/// to determine input file size upfront in order to know chunk size +/// to be written into each of N files/chunks: +/// * N split into N files based on size of input +/// * K/N output Kth of N to stdout +/// * l/N split into N files without splitting lines/records +/// * l/K/N output Kth of N to stdout without splitting lines/records +/// +/// For most files the size will be determined by either reading entire file content into a buffer +/// or by `len()` function of [`std::fs::metadata`]. +/// +/// However, for some files which report filesystem metadata size that does not match +/// their actual content size, we will need to attempt to find the end of file +/// with direct `seek()` on [`std::fs::File`]. +/// +/// For STDIN stream - read into a buffer up to a limit +/// If input stream does not EOF before that - return an error +/// (i.e. "infinite" input as in `cat /dev/zero | split ...`, `yes | split ...` etc.). +/// +/// Note: The `buf` might end up with either partial or entire input content. +fn get_input_size( + input: &String, + reader: &mut R, + buf: &mut Vec, + io_blksize: &Option, +) -> std::io::Result +where + R: BufRead, +{ + // Set read limit to io_blksize if specified + // Otherwise to OPT_IO_BLKSIZE_MAX + let read_limit = io_blksize.unwrap_or(OPT_IO_BLKSIZE_MAX) as u64; + + // Try to read into buffer up to a limit + let num_bytes = reader + .by_ref() + .take(read_limit) + .read_to_end(buf) + .map(|n| n as u64)?; + + if num_bytes < read_limit { + // Finite file or STDIN stream that fits entirely + // into a buffer within the limit + // Note: files like /dev/null or similar, + // empty STDIN stream, + // and files with true file size 0 + // will also fit here + Ok(num_bytes) + } else if input == "-" { + // STDIN stream that did not fit all content into a buffer + // Most likely continuous/infinite input stream + return Err(io::Error::new( + ErrorKind::Other, + format!("{}: cannot determine input size", input), + )); + } else { + // Could be that file size is larger than set read limit + // Get the file size from filesystem metadata + let metadata = metadata(input)?; + let metadata_size = metadata.len(); + if num_bytes <= metadata_size { + Ok(metadata_size) + } else { + // Could be a file from locations like /dev, /sys, /proc or similar + // which report filesystem metadata size that does not match + // their actual content size + // Attempt direct `seek()` for the end of a file + let mut tmp_fd = File::open(Path::new(input))?; + let end = tmp_fd.seek(SeekFrom::End(0))?; + if end > 0 { + Ok(end) + } else { + // Edge case of either "infinite" file (i.e. /dev/zero) + // or some other "special" non-standard file type + // Give up and return an error + // TODO It might be possible to do more here + // to address all possible file types and edge cases + return Err(io::Error::new( + ErrorKind::Other, + format!("{}: cannot determine file size", input), + )); + } + } + } +} + /// Write a certain number of bytes to one file, then move on to another one. /// /// This struct maintains an underlying writer representing the @@ -1018,155 +1130,110 @@ impl<'a> Write for LineBytesChunkWriter<'a> { } } -/// Split a file into a specific number of chunks by byte. +/// Split a file or STDIN into a specific number of chunks by byte. +/// If in Kth chunk of N mode - print the k-th chunk to STDOUT. /// -/// This function always creates one output file for each chunk, even +/// When file size cannot be evenly divided into the number of chunks of the same size, +/// the first X chunks are 1 byte longer than the rest, +/// where X is a modulus reminder of (file size % number of chunks) +/// +/// In Kth chunk of N mode - writes to stdout the contents of the chunk identified by `kth_chunk` +/// +/// In N chunks mode - this function always creates one output file for each chunk, even /// if there is an error reading or writing one of the chunks or if -/// the input file is truncated. However, if the `filter` option is -/// being used, then no files are created. +/// the input file is truncated. However, if the `--filter` option is +/// being used, then files will only be created if `$FILE` variable was used +/// in filter command, +/// i.e. `split -n 10 --filter='head -c1 > $FILE' in` /// /// # Errors /// /// This function returns an error if there is a problem reading from -/// `reader` or writing to one of the output files. +/// `reader` or writing to one of the output files or stdout. +/// +/// # See also +/// +/// * [`n_chunks_by_line`], which splits its input into a specific number of chunks by line. /// /// Implements `--number=CHUNKS` /// Where CHUNKS /// * N -fn split_into_n_chunks_by_byte( +/// * K/N +fn n_chunks_by_byte( settings: &Settings, reader: &mut R, num_chunks: u64, + kth_chunk: Option, ) -> UResult<()> where - R: Read, + R: BufRead, { - // Get the size of the input file in bytes and compute the number - // of bytes per chunk. - // + // Get the size of the input in bytes + let initial_buf = &mut Vec::new(); + let mut num_bytes = get_input_size(&settings.input, reader, initial_buf, &settings.io_blksize)?; + let mut reader = initial_buf.chain(reader); + + // If input file is empty and we would not have determined the Kth chunk + // in the Kth chunk of N chunk mode, then terminate immediately. + // This happens on `split -n 3/10 /dev/null`, for example. + if kth_chunk.is_some() && num_bytes == 0 { + return Ok(()); + } + // If the requested number of chunks exceeds the number of bytes - // in the file *and* the `elide_empty_files` parameter is enabled, - // then behave as if the number of chunks was set to the number of - // bytes in the file. This ensures that we don't write empty - // files. Otherwise, just write the `num_chunks - num_bytes` empty - // files. - let metadata = metadata(&settings.input).map_err(|_| { - USimpleError::new(1, format!("{}: cannot determine file size", settings.input)) - })?; - - let num_bytes = metadata.len(); - let will_have_empty_files = settings.elide_empty_files && num_chunks > num_bytes; - let (num_chunks, chunk_size) = if will_have_empty_files { - let num_chunks = num_bytes; - let chunk_size = 1; - (num_chunks, chunk_size) + // in the input: + // * in Kth chunk of N mode - just write empty byte string to stdout + // NOTE: the `elide_empty_files` parameter is ignored here + // as we do not generate any files + // and instead writing to stdout + // * In N chunks mode - if the `elide_empty_files` parameter is enabled, + // then behave as if the number of chunks was set to the number of + // bytes in the file. This ensures that we don't write empty + // files. Otherwise, just write the `num_chunks - num_bytes` empty files. + let num_chunks = if kth_chunk.is_none() && settings.elide_empty_files && num_chunks > num_bytes + { + num_bytes } else { - let chunk_size = (num_bytes / (num_chunks)).max(1); - (num_chunks, chunk_size) + num_chunks }; // If we would have written zero chunks of output, then terminate // immediately. This happens on `split -e -n 3 /dev/null`, for // example. - if num_chunks == 0 || num_bytes == 0 { + if num_chunks == 0 { return Ok(()); } - let num_chunks: usize = num_chunks - .try_into() - .map_err(|_| USimpleError::new(1, "Number of chunks too big"))?; - - // This object is responsible for creating the filename for each chunk. - let mut filename_iterator = FilenameIterator::new(&settings.prefix, &settings.suffix)?; - - // Create one writer for each chunk. This will create each - // of the underlying files (if not in `--filter` mode). + // In Kth chunk of N mode - we will write to stdout instead of to a file. + let mut stdout_writer = std::io::stdout().lock(); + // In N chunks mode - we will write to `num_chunks` files let mut writers = vec![]; - for _ in 0..num_chunks { - let filename = filename_iterator - .next() - .ok_or_else(|| USimpleError::new(1, "output file suffixes exhausted"))?; - let writer = settings.instantiate_current_writer(filename.as_str())?; - writers.push(writer); - } - - // Write `chunk_size` bytes from the reader into each writer - // except the last. - // - // The last writer gets all remaining bytes so that if the number - // of bytes in the input file was not evenly divisible by - // `num_chunks`, we don't leave any bytes behind. - for writer in writers.iter_mut().take(num_chunks - 1) { - match io::copy(&mut reader.by_ref().take(chunk_size), writer) { - Ok(_) => continue, - Err(e) if ignorable_io_error(&e, settings) => continue, - Err(e) => return Err(uio_error!(e, "input/output error")), - }; - } - - // Write all the remaining bytes to the last chunk. - let i = num_chunks - 1; - let last_chunk_size = num_bytes - (chunk_size * (num_chunks as u64 - 1)); - match io::copy(&mut reader.by_ref().take(last_chunk_size), &mut writers[i]) { - Ok(_) => Ok(()), - Err(e) if ignorable_io_error(&e, settings) => Ok(()), - Err(e) => Err(uio_error!(e, "input/output error")), - } -} -/// Print the k-th chunk of a file to stdout, splitting by byte. -/// -/// This function is like [`split_into_n_chunks_by_byte`], but instead -/// of writing each chunk to its own file, it only writes to stdout -/// the contents of the chunk identified by `chunk_number` -/// -/// # Errors -/// -/// This function returns an error if there is a problem reading from -/// `reader` or writing to stdout. -/// -/// Implements `--number=CHUNKS` -/// Where CHUNKS -/// * K/N -fn kth_chunks_by_byte( - settings: &Settings, - reader: &mut R, - chunk_number: u64, - num_chunks: u64, -) -> UResult<()> -where - R: BufRead, -{ - // Get the size of the input file in bytes and compute the number - // of bytes per chunk. - // - // If the requested number of chunks exceeds the number of bytes - // in the file - just write empty byte string to stdout - // NOTE: the `elide_empty_files` parameter is ignored here - // as we do not generate any files - // and instead writing to stdout - let metadata = metadata(&settings.input).map_err(|_| { - USimpleError::new(1, format!("{}: cannot determine file size", settings.input)) - })?; - - let num_bytes = metadata.len(); - // If input file is empty and we would have written zero chunks of output, - // then terminate immediately. - // This happens on `split -e -n 3 /dev/null`, for example. - if num_bytes == 0 { - return Ok(()); + // Calculate chunk size base and modulo reminder + // to be used in calculating chunk_size later on + let chunk_size_base = num_bytes / num_chunks; + let chunk_size_reminder = num_bytes % num_chunks; + + // If in N chunks mode + // Create one writer for each chunk. + // This will create each of the underlying files + // or stdin pipes to child shell/command processes if in `--filter` mode + if kth_chunk.is_none() { + // This object is responsible for creating the filename for each chunk. + let mut filename_iterator = FilenameIterator::new(&settings.prefix, &settings.suffix) + .map_err(|e| io::Error::new(ErrorKind::Other, format!("{e}")))?; + for _ in 0..num_chunks { + let filename = filename_iterator + .next() + .ok_or_else(|| USimpleError::new(1, "output file suffixes exhausted"))?; + let writer = settings.instantiate_current_writer(filename.as_str())?; + writers.push(writer); + } } - // Write to stdout instead of to a file. - let stdout = std::io::stdout(); - let mut writer = stdout.lock(); - - let chunk_size = (num_bytes / (num_chunks)).max(1); - let mut num_bytes: usize = num_bytes.try_into().unwrap(); - - let mut i = 1; - loop { - let buf: &mut Vec = &mut vec![]; + for i in 1_u64..=num_chunks { + let chunk_size = chunk_size_base + (chunk_size_reminder > i - 1) as u64; + let buf = &mut Vec::new(); if num_bytes > 0 { // Read `chunk_size` bytes from the reader into `buf` // except the last. @@ -1176,15 +1243,17 @@ where // `num_chunks`, we don't leave any bytes behind. let limit = { if i == num_chunks { - num_bytes.try_into().unwrap() + num_bytes } else { chunk_size } }; + let n_bytes_read = reader.by_ref().take(limit).read_to_end(buf); + match n_bytes_read { Ok(n_bytes) => { - num_bytes -= n_bytes; + num_bytes -= n_bytes as u64; } Err(error) => { return Err(USimpleError::new( @@ -1193,11 +1262,20 @@ where )); } } - if i == chunk_number { - writer.write_all(buf)?; - break; + + match kth_chunk { + Some(chunk_number) => { + if i == chunk_number { + stdout_writer.write_all(buf)?; + break; + } + } + None => { + let idx = (i - 1) as usize; + let writer = writers.get_mut(idx).unwrap(); + writer.write_all(buf)?; + } } - i += 1; } else { break; } @@ -1205,12 +1283,17 @@ where Ok(()) } -/// Split a file into a specific number of chunks by line. +/// Split a file or STDIN into a specific number of chunks by line. +/// If in Kth chunk of N mode - print the k-th chunk to STDOUT. +/// +/// In Kth chunk of N mode - writes to stdout the contents of the chunk identified by `kth_chunk` /// -/// This function always creates one output file for each chunk, even +/// In N chunks mode - this function always creates one output file for each chunk, even /// if there is an error reading or writing one of the chunks or if -/// the input file is truncated. However, if the `filter` option is -/// being used, then no files are created. +/// the input file is truncated. However, if the `--filter` option is +/// being used, then files will only be created if `$FILE` variable was used +/// in filter command, +/// i.e. `split -n l/10 --filter='head -c1 > $FILE' in` /// /// # Errors /// @@ -1219,119 +1302,82 @@ where /// /// # See also /// -/// * [`kth_chunk_by_line`], which splits its input in the same way, -/// but writes only one specified chunk to stdout. +/// * [`n_chunks_by_byte`], which splits its input into a specific number of chunks by byte. /// /// Implements `--number=CHUNKS` /// Where CHUNKS /// * l/N -fn split_into_n_chunks_by_line( +/// * l/K/N +fn n_chunks_by_line( settings: &Settings, reader: &mut R, num_chunks: u64, + kth_chunk: Option, ) -> UResult<()> where R: BufRead, { - // Get the size of the input file in bytes and compute the number + // Get the size of the input in bytes and compute the number // of bytes per chunk. - let metadata = metadata(&settings.input).map_err(|_| { - USimpleError::new(1, format!("{}: cannot determine file size", settings.input)) - })?; - let num_bytes = metadata.len(); + let initial_buf = &mut Vec::new(); + let num_bytes = get_input_size(&settings.input, reader, initial_buf, &settings.io_blksize)?; + let reader = initial_buf.chain(reader); let chunk_size = (num_bytes / num_chunks) as usize; - // This object is responsible for creating the filename for each chunk. - let mut filename_iterator = FilenameIterator::new(&settings.prefix, &settings.suffix)?; - - // Create one writer for each chunk. This will create each - // of the underlying files (if not in `--filter` mode). - let mut writers = vec![]; - for _ in 0..num_chunks { - let filename = filename_iterator - .next() - .ok_or_else(|| USimpleError::new(1, "output file suffixes exhausted"))?; - let writer = settings.instantiate_current_writer(filename.as_str())?; - writers.push(writer); + // If input file is empty and we would not have determined the Kth chunk + // in the Kth chunk of N chunk mode, then terminate immediately. + // This happens on `split -n l/3/10 /dev/null`, for example. + if kth_chunk.is_some() && num_bytes == 0 { + return Ok(()); } - let mut num_bytes_remaining_in_current_chunk = chunk_size; - let mut i = 0; - let sep = settings.separator; - for line_result in reader.split(sep) { - let line = line_result.unwrap(); - let maybe_writer = writers.get_mut(i); - let writer = maybe_writer.unwrap(); - let bytes = line.as_slice(); - custom_write_all(bytes, writer, settings)?; - custom_write_all(&[sep], writer, settings)?; + // In Kth chunk of N mode - we will write to stdout instead of to a file. + let mut stdout_writer = std::io::stdout().lock(); + // In N chunks mode - we will write to `num_chunks` files + let mut writers = vec![]; - // Add one byte for the separator character. - let num_bytes = bytes.len() + 1; - if num_bytes > num_bytes_remaining_in_current_chunk { - num_bytes_remaining_in_current_chunk = chunk_size; - i += 1; - } else { - num_bytes_remaining_in_current_chunk -= num_bytes; + // If in N chunks mode + // Create one writer for each chunk. + // This will create each of the underlying files + // or stdin pipes to child shell/command processes if in `--filter` mode + if kth_chunk.is_none() { + // This object is responsible for creating the filename for each chunk. + let mut filename_iterator = FilenameIterator::new(&settings.prefix, &settings.suffix) + .map_err(|e| io::Error::new(ErrorKind::Other, format!("{e}")))?; + for _ in 0..num_chunks { + let filename = filename_iterator + .next() + .ok_or_else(|| USimpleError::new(1, "output file suffixes exhausted"))?; + let writer = settings.instantiate_current_writer(filename.as_str())?; + writers.push(writer); } } - Ok(()) -} - -/// Print the k-th chunk of a file, splitting by line. -/// -/// This function is like [`split_into_n_chunks_by_line`], but instead -/// of writing each chunk to its own file, it only writes to stdout -/// the contents of the chunk identified by `chunk_number`. -/// -/// # Errors -/// -/// This function returns an error if there is a problem reading from -/// `reader` or writing to one of the output files. -/// -/// # See also -/// -/// * [`split_into_n_chunks_by_line`], which splits its input in the -/// same way, but writes each chunk to its own file. -/// -/// Implements `--number=CHUNKS` -/// Where CHUNKS -/// * l/K/N -fn kth_chunk_by_line( - settings: &Settings, - reader: &mut R, - chunk_number: u64, - num_chunks: u64, -) -> UResult<()> -where - R: BufRead, -{ - // Get the size of the input file in bytes and compute the number - // of bytes per chunk. - let metadata = metadata(&settings.input).map_err(|_| { - USimpleError::new(1, format!("{}: cannot determine file size", settings.input)) - })?; - let num_bytes = metadata.len(); - let chunk_size = (num_bytes / num_chunks) as usize; - - // Write to stdout instead of to a file. - let stdout = std::io::stdout(); - let mut writer = stdout.lock(); - let mut num_bytes_remaining_in_current_chunk = chunk_size; let mut i = 1; let sep = settings.separator; + for line_result in reader.split(sep) { - let line = line_result?; + // add separator back in at the end of the line + let mut line = line_result?; + line.push(sep); let bytes = line.as_slice(); - if i == chunk_number { - writer.write_all(bytes)?; - writer.write_all(&[sep])?; + + match kth_chunk { + Some(chunk_number) => { + if i == chunk_number { + stdout_writer.write_all(bytes)?; + } + } + None => { + let idx = (i - 1) as usize; + let maybe_writer = writers.get_mut(idx); + let writer = maybe_writer.unwrap(); + custom_write_all(bytes, writer, settings)?; + } } - // Add one byte for the separator character. - let num_bytes = bytes.len() + 1; + let num_bytes = bytes.len(); if num_bytes >= num_bytes_remaining_in_current_chunk { num_bytes_remaining_in_current_chunk = chunk_size; i += 1; @@ -1339,21 +1385,27 @@ where num_bytes_remaining_in_current_chunk -= num_bytes; } - if i > chunk_number { - break; + if let Some(chunk_number) = kth_chunk { + if i > chunk_number { + break; + } } } Ok(()) } -/// Split a file into a specific number of chunks by line, but +/// Split a file or STDIN into a specific number of chunks by line, but /// assign lines via round-robin /// -/// This function always creates one output file for each chunk, even +/// In Kth chunk of N mode - writes to stdout the contents of the chunk identified by `kth_chunk` +/// +/// In N chunks mode - this function always creates one output file for each chunk, even /// if there is an error reading or writing one of the chunks or if -/// the input file is truncated. However, if the `filter` option is -/// being used, then no files are created. +/// the input file is truncated. However, if the `--filter` option is +/// being used, then files will only be created if `$FILE` variable was used +/// in filter command, +/// i.e. `split -n r/10 --filter='head -c1 > $FILE' in` /// /// # Errors /// @@ -1362,50 +1414,73 @@ where /// /// # See also /// -/// * [`split_into_n_chunks_by_line`], which splits its input in the same way, -/// but without round robin distribution. +/// * [`n_chunks_by_line`], which splits its input into a specific number of chunks by line. /// /// Implements `--number=CHUNKS` /// Where CHUNKS /// * r/N -fn split_into_n_chunks_by_line_round_robin( +/// * r/K/N +fn n_chunks_by_line_round_robin( settings: &Settings, reader: &mut R, num_chunks: u64, + kth_chunk: Option, ) -> UResult<()> where R: BufRead, { - // This object is responsible for creating the filename for each chunk. - let mut filename_iterator = FilenameIterator::new(&settings.prefix, &settings.suffix) - .map_err(|e| io::Error::new(ErrorKind::Other, format!("{e}")))?; - - // Create one writer for each chunk. This will create each - // of the underlying files (if not in `--filter` mode). + // In Kth chunk of N mode - we will write to stdout instead of to a file. + let mut stdout_writer = std::io::stdout().lock(); + // In N chunks mode - we will write to `num_chunks` files let mut writers = vec![]; - for _ in 0..num_chunks { - let filename = filename_iterator - .next() - .ok_or_else(|| io::Error::new(ErrorKind::Other, "output file suffixes exhausted"))?; - let writer = settings.instantiate_current_writer(filename.as_str())?; - writers.push(writer); + + // If in N chunks mode + // Create one writer for each chunk. + // This will create each of the underlying files + // or stdin pipes to child shell/command processes if in `--filter` mode + if kth_chunk.is_none() { + // This object is responsible for creating the filename for each chunk. + let mut filename_iterator = FilenameIterator::new(&settings.prefix, &settings.suffix) + .map_err(|e| io::Error::new(ErrorKind::Other, format!("{e}")))?; + for _ in 0..num_chunks { + let filename = filename_iterator + .next() + .ok_or_else(|| USimpleError::new(1, "output file suffixes exhausted"))?; + let writer = settings.instantiate_current_writer(filename.as_str())?; + writers.push(writer); + } } let num_chunks: usize = num_chunks.try_into().unwrap(); let sep = settings.separator; let mut closed_writers = 0; for (i, line_result) in reader.split(sep).enumerate() { - let maybe_writer = writers.get_mut(i % num_chunks); - let writer = maybe_writer.unwrap(); - let mut line = line_result.unwrap(); + // add separator back in at the end of the line + let mut line = line_result?; line.push(sep); let bytes = line.as_slice(); - let writer_stdin_open = custom_write_all(bytes, writer, settings)?; - if !writer_stdin_open { - closed_writers += 1; - if closed_writers == num_chunks { - // all writers are closed - stop reading - break; + + match kth_chunk { + Some(chunk_number) => { + // The `.enumerate()` method returns index `i` starting with 0, + // but chunk number is given as a 1-indexed number, + // so compare to `chunk_number - 1` + if (i % num_chunks) == (chunk_number - 1) as usize { + stdout_writer.write_all(bytes)?; + } + } + None => { + let maybe_writer = writers.get_mut(i % num_chunks); + let writer = maybe_writer.unwrap(); + + let writer_stdin_open = custom_write_all(bytes, writer, settings)?; + if !writer_stdin_open { + closed_writers += 1; + if closed_writers == num_chunks { + // all writers are closed - stop reading + break; + } + } } } } @@ -1413,62 +1488,9 @@ where Ok(()) } -/// Print the k-th chunk of a file, splitting by line, but -/// assign lines via round-robin to the specified number of output -/// chunks, but output only the *k*th chunk. -/// -/// This function is like [`kth_chunk_by_line`], as it only writes to stdout and -/// prints out only *k*th chunk -/// It is also like [`split_into_n_chunks_by_line_round_robin`], as it is assigning chunks -/// using round robin distribution -/// -/// # Errors -/// -/// This function returns an error if there is a problem reading from -/// `reader` or writing to one of the output files. -/// -/// # See also -/// -/// * [`split_into_n_chunks_by_line_round_robin`], which splits its input in the -/// same way, but writes each chunk to its own file. -/// -/// Implements `--number=CHUNKS` -/// Where CHUNKS -/// * r/K/N -fn kth_chunk_by_line_round_robin( - settings: &Settings, - reader: &mut R, - chunk_number: u64, - num_chunks: u64, -) -> UResult<()> -where - R: BufRead, -{ - // Write to stdout instead of to a file. - let stdout = std::io::stdout(); - let mut writer = stdout.lock(); - - let num_chunks: usize = num_chunks.try_into().unwrap(); - let chunk_number: usize = chunk_number.try_into().unwrap(); - let sep = settings.separator; - // The chunk number is given as a 1-indexed number, but it - // is a little easier to deal with a 0-indexed number - // since `.enumerate()` returns index `i` starting with 0 - let chunk_number = chunk_number - 1; - for (i, line_result) in reader.split(sep).enumerate() { - let line = line_result?; - let bytes = line.as_slice(); - if (i % num_chunks) == chunk_number { - writer.write_all(bytes)?; - writer.write_all(&[sep])?; - } - } - Ok(()) -} - #[allow(clippy::cognitive_complexity)] fn split(settings: &Settings) -> UResult<()> { - let mut reader = BufReader::new(if settings.input == "-" { + let r_box = if settings.input == "-" { Box::new(stdin()) as Box } else { let r = File::open(Path::new(&settings.input)).map_err_context(|| { @@ -1478,26 +1500,33 @@ fn split(settings: &Settings) -> UResult<()> { ) })?; Box::new(r) as Box - }); + }; + let mut reader = if let Some(c) = settings.io_blksize { + BufReader::with_capacity(c, r_box) + } else { + BufReader::new(r_box) + }; match settings.strategy { Strategy::Number(NumberType::Bytes(num_chunks)) => { - split_into_n_chunks_by_byte(settings, &mut reader, num_chunks) + // split_into_n_chunks_by_byte(settings, &mut reader, num_chunks) + n_chunks_by_byte(settings, &mut reader, num_chunks, None) } Strategy::Number(NumberType::KthBytes(chunk_number, num_chunks)) => { - kth_chunks_by_byte(settings, &mut reader, chunk_number, num_chunks) + // kth_chunks_by_byte(settings, &mut reader, chunk_number, num_chunks) + n_chunks_by_byte(settings, &mut reader, num_chunks, Some(chunk_number)) } Strategy::Number(NumberType::Lines(num_chunks)) => { - split_into_n_chunks_by_line(settings, &mut reader, num_chunks) + n_chunks_by_line(settings, &mut reader, num_chunks, None) } Strategy::Number(NumberType::KthLines(chunk_number, num_chunks)) => { - kth_chunk_by_line(settings, &mut reader, chunk_number, num_chunks) + n_chunks_by_line(settings, &mut reader, num_chunks, Some(chunk_number)) } Strategy::Number(NumberType::RoundRobin(num_chunks)) => { - split_into_n_chunks_by_line_round_robin(settings, &mut reader, num_chunks) + n_chunks_by_line_round_robin(settings, &mut reader, num_chunks, None) } Strategy::Number(NumberType::KthRoundRobin(chunk_number, num_chunks)) => { - kth_chunk_by_line_round_robin(settings, &mut reader, chunk_number, num_chunks) + n_chunks_by_line_round_robin(settings, &mut reader, num_chunks, Some(chunk_number)) } Strategy::Lines(chunk_size) => { let mut writer = LineChunkWriter::new(chunk_size, settings)?; diff --git a/tests/by-util/test_split.rs b/tests/by-util/test_split.rs index aec6f059441..0ae2af5cb92 100644 --- a/tests/by-util/test_split.rs +++ b/tests/by-util/test_split.rs @@ -2,7 +2,7 @@ // // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// spell-checker:ignore xzaaa sixhundredfiftyonebytes ninetyonebytes threebytes asciilowercase fghij klmno pqrst uvwxyz fivelines twohundredfortyonebytes onehundredlines nbbbb dxen ncccc +// spell-checker:ignore xzaaa sixhundredfiftyonebytes ninetyonebytes threebytes asciilowercase ghijkl mnopq rstuv wxyz fivelines twohundredfortyonebytes onehundredlines nbbbb dxen ncccc use crate::common::util::{AtPath, TestScenario}; use rand::{thread_rng, Rng, SeedableRng}; @@ -704,54 +704,41 @@ fn test_split_overflow_bytes_size() { assert_eq!(glob.collate(), at.read_bytes(name)); } -#[test] -#[cfg(target_pointer_width = "32")] -fn test_split_chunks_num_chunks_oversized_32() { - let scene = TestScenario::new(util_name!()); - let at = &scene.fixtures; - at.touch("file"); - scene - .ucmd() - .args(&["--number", "5000000000", "sixhundredfiftyonebytes.txt"]) - .fails() - .code_is(1) - .stderr_only("split: Number of chunks too big\n"); -} - #[test] fn test_split_stdin_num_chunks() { - new_ucmd!() - .args(&["--number=1"]) - .fails() - .code_is(1) - .stderr_only("split: -: cannot determine file size\n"); + let (at, mut ucmd) = at_and_ucmd!(); + ucmd.args(&["--number=1"]).pipe_in("").succeeds(); + assert_eq!(file_read(&at, "xaa"), ""); + assert!(!at.plus("xab").exists()); } #[test] fn test_split_stdin_num_kth_chunk() { new_ucmd!() .args(&["--number=1/2"]) - .fails() - .code_is(1) - .stderr_only("split: -: cannot determine file size\n"); + .pipe_in("1\n2\n3\n4\n5\n") + .succeeds() + .stdout_only("1\n2\n3"); } #[test] fn test_split_stdin_num_line_chunks() { - new_ucmd!() - .args(&["--number=l/2"]) - .fails() - .code_is(1) - .stderr_only("split: -: cannot determine file size\n"); + let (at, mut ucmd) = at_and_ucmd!(); + ucmd.args(&["--number=l/2"]) + .pipe_in("1\n2\n3\n4\n5\n") + .succeeds(); + assert_eq!(file_read(&at, "xaa"), "1\n2\n3\n"); + assert_eq!(file_read(&at, "xab"), "4\n5\n"); + assert!(!at.plus("xac").exists()); } #[test] fn test_split_stdin_num_kth_line_chunk() { new_ucmd!() .args(&["--number=l/2/5"]) - .fails() - .code_is(1) - .stderr_only("split: -: cannot determine file size\n"); + .pipe_in("1\n2\n3\n4\n5\n") + .succeeds() + .stdout_only("2\n"); } fn file_read(at: &AtPath, filename: &str) -> String { @@ -912,6 +899,14 @@ fn test_suffixes_exhausted() { .stderr_only("split: output file suffixes exhausted\n"); } +#[test] +fn test_suffix_length_req() { + new_ucmd!() + .args(&["-n", "100", "-a", "1", "asciilowercase.txt"]) + .fails() + .stderr_only("split: the suffix length needs to be at least 2\n"); +} + #[test] fn test_verbose() { new_ucmd!() @@ -937,11 +932,11 @@ fn test_number_n() { s }; ucmd.args(&["-n", "5", "asciilowercase.txt"]).succeeds(); - assert_eq!(file_read("xaa"), "abcde"); - assert_eq!(file_read("xab"), "fghij"); - assert_eq!(file_read("xac"), "klmno"); - assert_eq!(file_read("xad"), "pqrst"); - assert_eq!(file_read("xae"), "uvwxyz\n"); + assert_eq!(file_read("xaa"), "abcdef"); + assert_eq!(file_read("xab"), "ghijkl"); + assert_eq!(file_read("xac"), "mnopq"); + assert_eq!(file_read("xad"), "rstuv"); + assert_eq!(file_read("xae"), "wxyz\n"); #[cfg(unix)] new_ucmd!() .args(&["--number=100", "/dev/null"]) @@ -954,11 +949,11 @@ fn test_number_kth_of_n() { new_ucmd!() .args(&["--number=3/5", "asciilowercase.txt"]) .succeeds() - .stdout_only("klmno"); + .stdout_only("mnopq"); new_ucmd!() .args(&["--number=5/5", "asciilowercase.txt"]) .succeeds() - .stdout_only("uvwxyz\n"); + .stdout_only("wxyz\n"); new_ucmd!() .args(&["-e", "--number=99/100", "asciilowercase.txt"]) .succeeds() @@ -1046,11 +1041,11 @@ fn test_split_number_with_io_blksize() { }; ucmd.args(&["-n", "5", "asciilowercase.txt", "---io-blksize", "1024"]) .succeeds(); - assert_eq!(file_read("xaa"), "abcde"); - assert_eq!(file_read("xab"), "fghij"); - assert_eq!(file_read("xac"), "klmno"); - assert_eq!(file_read("xad"), "pqrst"); - assert_eq!(file_read("xae"), "uvwxyz\n"); + assert_eq!(file_read("xaa"), "abcdef"); + assert_eq!(file_read("xab"), "ghijkl"); + assert_eq!(file_read("xac"), "mnopq"); + assert_eq!(file_read("xad"), "rstuv"); + assert_eq!(file_read("xae"), "wxyz\n"); } #[test] @@ -1065,6 +1060,32 @@ fn test_split_default_with_io_blksize() { assert_eq!(glob.collate(), at.read_bytes(name)); } +#[test] +fn test_split_invalid_io_blksize() { + new_ucmd!() + .args(&["---io-blksize=XYZ", "threebytes.txt"]) + .fails() + .stderr_only("split: invalid IO block size: 'XYZ'\n"); + new_ucmd!() + .args(&["---io-blksize=5000000000", "threebytes.txt"]) + .fails() + .stderr_only("split: invalid IO block size: '5000000000'\n"); + #[cfg(target_pointer_width = "32")] + new_ucmd!() + .args(&["---io-blksize=2146435072", "threebytes.txt"]) + .fails() + .stderr_only("split: invalid IO block size: '2146435072'\n"); +} + +#[test] +fn test_split_number_oversized_stdin() { + new_ucmd!() + .args(&["--number=3", "---io-blksize=600"]) + .pipe_in_fixture("sixhundredfiftyonebytes.txt") + .fails() + .stderr_only("split: -: cannot determine input size\n"); +} + #[test] fn test_invalid_suffix_length() { new_ucmd!() @@ -1157,6 +1178,18 @@ fn test_elide_dev_null() { assert!(!at.plus("xac").exists()); } +#[test] +#[cfg(unix)] +fn test_dev_zero() { + let (at, mut ucmd) = at_and_ucmd!(); + ucmd.args(&["-n", "3", "/dev/zero"]) + .fails() + .stderr_only("split: /dev/zero: cannot determine file size\n"); + assert!(!at.plus("xaa").exists()); + assert!(!at.plus("xab").exists()); + assert!(!at.plus("xac").exists()); +} + #[test] fn test_lines() { let (at, mut ucmd) = at_and_ucmd!(); @@ -1182,6 +1215,15 @@ fn test_lines_kth() { .stdout_only("20\n21\n22\n23\n24\n25\n26\n27\n28\n29\n"); } +#[test] +#[cfg(unix)] +fn test_lines_kth_dev_null() { + new_ucmd!() + .args(&["-n", "l/3/10", "/dev/null"]) + .succeeds() + .stdout_only(""); +} + #[test] fn test_line_bytes() { let (at, mut ucmd) = at_and_ucmd!(); @@ -1321,7 +1363,7 @@ fn test_numeric_suffix() { } #[test] -fn test_numeric_suffix_alias() { +fn test_numeric_suffix_inferred() { let (at, mut ucmd) = at_and_ucmd!(); ucmd.args(&["-n", "4", "--numeric=9", "threebytes.txt"]) .succeeds() From 8e796d3bb679bf7c064baca79b2ee0664b90762e Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Sun, 19 Nov 2023 15:10:28 +0100 Subject: [PATCH 0372/2851] mv: fix issue with -T and dest ending with "/" --- src/uu/mv/src/mv.rs | 2 +- tests/by-util/test_mv.rs | 26 ++++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/src/uu/mv/src/mv.rs b/src/uu/mv/src/mv.rs index 036024f9907..f0529af7e6c 100644 --- a/src/uu/mv/src/mv.rs +++ b/src/uu/mv/src/mv.rs @@ -341,7 +341,7 @@ fn handle_two_paths(source: &Path, target: &Path, opts: &Options) -> UResult<()> let target_is_dir = target.is_dir(); - if path_ends_with_terminator(target) && !target_is_dir { + if path_ends_with_terminator(target) && !target_is_dir && !opts.no_target_dir { return Err(MvError::FailedToAccessNotADirectory(target.quote().to_string()).into()); } diff --git a/tests/by-util/test_mv.rs b/tests/by-util/test_mv.rs index c54d24ea906..571de769125 100644 --- a/tests/by-util/test_mv.rs +++ b/tests/by-util/test_mv.rs @@ -1158,6 +1158,32 @@ fn test_mv_overwrite_dir() { assert!(at.dir_exists(dir_b)); } +#[test] +fn test_mv_no_target_dir_with_dest_not_existing() { + let (at, mut ucmd) = at_and_ucmd!(); + let dir_a = "a"; + let dir_b = "b"; + + at.mkdir(dir_a); + ucmd.arg("-T").arg(dir_a).arg(dir_b).succeeds().no_output(); + + assert!(!at.dir_exists(dir_a)); + assert!(at.dir_exists(dir_b)); +} + +#[test] +fn test_mv_no_target_dir_with_dest_not_existing_and_ending_with_slash() { + let (at, mut ucmd) = at_and_ucmd!(); + let dir_a = "a"; + let dir_b = "b/"; + + at.mkdir(dir_a); + ucmd.arg("-T").arg(dir_a).arg(dir_b).succeeds().no_output(); + + assert!(!at.dir_exists(dir_a)); + assert!(at.dir_exists(dir_b)); +} + #[test] fn test_mv_overwrite_nonempty_dir() { let (at, mut ucmd) = at_and_ucmd!(); From 285e580b6eb9475e5d61645594b7a75c13073eed Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Sun, 19 Nov 2023 15:59:38 +0100 Subject: [PATCH 0373/2851] cp: improve error msg if -r is not specified --- src/uu/cp/src/copydir.rs | 2 +- tests/by-util/test_cp.rs | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/uu/cp/src/copydir.rs b/src/uu/cp/src/copydir.rs index 763d66c0b03..a903ed2aaff 100644 --- a/src/uu/cp/src/copydir.rs +++ b/src/uu/cp/src/copydir.rs @@ -324,7 +324,7 @@ pub(crate) fn copy_directory( source_in_command_line: bool, ) -> CopyResult<()> { if !options.recursive { - return Err(format!("omitting directory {}", root.quote()).into()); + return Err(format!("-r not specified; omitting directory {}", root.quote()).into()); } // if no-dereference is enabled and this is a symlink, copy it as a file diff --git a/tests/by-util/test_cp.rs b/tests/by-util/test_cp.rs index c8761fab8fb..14b68da3718 100644 --- a/tests/by-util/test_cp.rs +++ b/tests/by-util/test_cp.rs @@ -131,7 +131,9 @@ fn test_cp_directory_not_recursive() { .arg(TEST_COPY_TO_FOLDER) .arg(TEST_HELLO_WORLD_DEST) .fails() - .stderr_contains("omitting directory"); + .stderr_is(format!( + "cp: -r not specified; omitting directory '{TEST_COPY_TO_FOLDER}'\n" + )); } #[test] From 58087df02a30bb4c7d6e474dfeee20b9faa51b93 Mon Sep 17 00:00:00 2001 From: clara swanson <69856940+cswn@users.noreply.github.com> Date: Mon, 20 Nov 2023 10:16:18 +0100 Subject: [PATCH 0374/2851] stdbuf: remove crash macro (#5549) * stdbuf: remove crash! macro * stdbuf: change target_vendor back to apple * tests/stdbuf: change stderr_only to usage_error in test_stdbuf_invalid_mode_fails * stdbuf: add exit code to check_option * stdbuf: remove set_exit_code line from error --- src/uu/stdbuf/src/stdbuf.rs | 29 +++++++++++++++-------------- tests/by-util/test_stdbuf.rs | 2 +- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/uu/stdbuf/src/stdbuf.rs b/src/uu/stdbuf/src/stdbuf.rs index 8578282752e..38c4451ca50 100644 --- a/src/uu/stdbuf/src/stdbuf.rs +++ b/src/uu/stdbuf/src/stdbuf.rs @@ -7,7 +7,7 @@ use clap::{crate_version, Arg, ArgAction, ArgMatches, Command}; use std::fs::File; -use std::io::{self, Write}; +use std::io::Write; use std::os::unix::process::ExitStatusExt; use std::path::PathBuf; use std::process; @@ -15,7 +15,7 @@ use tempfile::tempdir; use tempfile::TempDir; use uucore::error::{FromIo, UResult, USimpleError, UUsageError}; use uucore::parse_size::parse_size_u64; -use uucore::{crash, format_usage, help_about, help_section, help_usage}; +use uucore::{format_usage, help_about, help_section, help_usage}; const ABOUT: &str = help_about!("stdbuf.md"); const USAGE: &str = help_usage!("stdbuf.md"); @@ -66,13 +66,13 @@ struct ProgramOptionsError(String); target_os = "netbsd", target_os = "dragonflybsd" ))] -fn preload_strings() -> (&'static str, &'static str) { - ("LD_PRELOAD", "so") +fn preload_strings() -> UResult<(&'static str, &'static str)> { + Ok(("LD_PRELOAD", "so")) } #[cfg(target_vendor = "apple")] -fn preload_strings() -> (&'static str, &'static str) { - ("DYLD_LIBRARY_PATH", "dylib") +fn preload_strings() -> UResult<(&'static str, &'static str)> { + Ok(("DYLD_LIBRARY_PATH", "dylib")) } #[cfg(not(any( @@ -83,10 +83,11 @@ fn preload_strings() -> (&'static str, &'static str) { target_os = "dragonflybsd", target_vendor = "apple" )))] -fn preload_strings() -> (&'static str, &'static str) { - use uucore::crash; - - crash!(1, "Command not supported for this operating system!") +fn preload_strings() -> UResult<(&'static str, &'static str)> { + Err(USimpleError::new( + 1, + "Command not supported for this operating system!", + )) } fn check_option(matches: &ArgMatches, name: &str) -> Result { @@ -102,7 +103,7 @@ fn check_option(matches: &ArgMatches, name: &str) -> Result parse_size_u64(x).map_or_else( - |e| crash!(125, "invalid mode {}", e), + |e| Err(ProgramOptionsError(format!("invalid mode {e}"))), |m| { Ok(BufferType::Size(m.try_into().map_err(|_| { ProgramOptionsError(format!( @@ -128,8 +129,8 @@ fn set_command_env(command: &mut process::Command, buffer_name: &str, buffer_typ } } -fn get_preload_env(tmp_dir: &TempDir) -> io::Result<(String, PathBuf)> { - let (preload, extension) = preload_strings(); +fn get_preload_env(tmp_dir: &TempDir) -> UResult<(String, PathBuf)> { + let (preload, extension) = preload_strings()?; let inject_path = tmp_dir.path().join("libstdbuf").with_extension(extension); let mut file = File::create(&inject_path)?; @@ -151,7 +152,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let command_params: Vec<&str> = command_values.map(|s| s.as_ref()).collect(); let tmp_dir = tempdir().unwrap(); - let (preload_env, libstdbuf) = get_preload_env(&tmp_dir).map_err_context(String::new)?; + let (preload_env, libstdbuf) = get_preload_env(&tmp_dir)?; command.env(preload_env, libstdbuf); set_command_env(&mut command, "_STDBUF_I", &options.stdin); set_command_env(&mut command, "_STDBUF_O", &options.stdout); diff --git a/tests/by-util/test_stdbuf.rs b/tests/by-util/test_stdbuf.rs index 9a67dad9e37..50de4c54696 100644 --- a/tests/by-util/test_stdbuf.rs +++ b/tests/by-util/test_stdbuf.rs @@ -65,7 +65,7 @@ fn test_stdbuf_invalid_mode_fails() { .args(&[*option, "1024R", "head"]) .fails() .code_is(125) - .stderr_only("stdbuf: invalid mode '1024R': Value too large for defined data type\n"); + .usage_error("invalid mode '1024R': Value too large for defined data type"); #[cfg(not(target_pointer_width = "128"))] new_ucmd!() .args(&[*option, "1Y", "head"]) From c43ee01d1920eeeb98d2cc38f61ef217ef89cf12 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Mon, 20 Nov 2023 12:38:26 +0100 Subject: [PATCH 0375/2851] printf: allow precision in string --- src/uucore/src/lib/features/format/spec.rs | 23 ++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/src/uucore/src/lib/features/format/spec.rs b/src/uucore/src/lib/features/format/spec.rs index 06f0ca1d6e7..0cd0f03b4c3 100644 --- a/src/uucore/src/lib/features/format/spec.rs +++ b/src/uucore/src/lib/features/format/spec.rs @@ -17,6 +17,7 @@ pub enum Spec { }, String { width: Option>, + precision: Option>, parse_escape: bool, align_left: bool, }, @@ -159,11 +160,13 @@ impl Spec { }, b's' => Spec::String { width, + precision, parse_escape: false, align_left: minus, }, b'b' => Spec::String { width, + precision, parse_escape: true, align_left: minus, }, @@ -254,10 +257,12 @@ impl Spec { } &Spec::String { width, + precision, parse_escape, align_left, } => { let width = resolve_asterisk(width, &mut args)?.unwrap_or(0); + let precision = resolve_asterisk(precision, &mut args)?; let arg = next_arg(&mut args)?; let Some(s) = arg.get_str() else { return Err(FormatError::InvalidArgument(arg.clone())); @@ -273,15 +278,29 @@ impl Spec { } }; } + // GNU does do this truncation on a byte level, see for instance: + // printf "%.1s" 🙃 + // > � + // For now, we let printf panic when we truncate within a code point. + // TODO: We need to not use Rust's formatting for aligning the output, + // so that we can just write bytes to stdout without panicking. + let truncated = match precision { + Some(p) if p < parsed.len() => &parsed[..p], + _ => &parsed, + }; write_padded( writer, - std::str::from_utf8(&parsed).expect("TODO: Accept invalid utf8"), + std::str::from_utf8(&truncated).expect("TODO: Accept invalid utf8"), width, false, align_left, ) } else { - write_padded(writer, s, width, false, align_left) + let truncated = match precision { + Some(p) if p < s.len() => &s[..p], + _ => s, + }; + write_padded(writer, truncated, width, false, align_left) } } &Spec::SignedInt { From 066d8ba73d0a08eb47c1fea9e1d446b47fbca1f6 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Mon, 20 Nov 2023 13:25:20 +0100 Subject: [PATCH 0376/2851] printf: coerce missing and invalid arguments to 0 --- .../src/lib/features/format/argument.rs | 113 ++++++++++-------- src/uucore/src/lib/features/format/spec.rs | 52 ++------ 2 files changed, 75 insertions(+), 90 deletions(-) diff --git a/src/uucore/src/lib/features/format/argument.rs b/src/uucore/src/lib/features/format/argument.rs index 120b59aa4b5..96cfeddf3f6 100644 --- a/src/uucore/src/lib/features/format/argument.rs +++ b/src/uucore/src/lib/features/format/argument.rs @@ -9,84 +9,103 @@ pub enum FormatArgument { Unparsed(String), } -impl FormatArgument { - pub fn get_char(&self) -> Option { - match self { - Self::Char(c) => Some(*c), - Self::Unparsed(s) => { +pub trait ArgumentIter<'a>: Iterator { + fn get_char(&mut self) -> char; + fn get_i64(&mut self) -> i64; + fn get_u64(&mut self) -> u64; + fn get_f64(&mut self) -> f64; + fn get_str(&mut self) -> &'a str; +} + +impl<'a, T: Iterator> ArgumentIter<'a> for T { + fn get_char(&mut self) -> char { + let Some(next) = self.next() else { + return '\0'; + }; + match next { + FormatArgument::Char(c) => *c, + FormatArgument::Unparsed(s) => { let mut chars = s.chars(); let Some(c) = chars.next() else { - return None; + return '\0'; }; let None = chars.next() else { - return None; + return '\0'; }; - Some(c) + c } - _ => None, + _ => '\0', } } - pub fn get_u64(&self) -> Option { - match self { - Self::UnsignedInt(n) => Some(*n), - Self::Unparsed(s) => { - if let Some(s) = s.strip_prefix("0x") { - u64::from_str_radix(s, 16).ok() - } else if let Some(s) = s.strip_prefix("0") { - u64::from_str_radix(s, 8).ok() - } else if let Some(s) = s.strip_prefix('\'') { - Some(s.chars().next()? as u64) - } else { - s.parse().ok() - } + fn get_u64(&mut self) -> u64 { + let Some(next) = self.next() else { + return 0; + }; + match next { + FormatArgument::UnsignedInt(n) => *n, + FormatArgument::Unparsed(s) => if let Some(s) = s.strip_prefix("0x") { + u64::from_str_radix(s, 16).ok() + } else if let Some(s) = s.strip_prefix("0") { + u64::from_str_radix(s, 8).ok() + } else if let Some(s) = s.strip_prefix('\'') { + s.chars().next().map(|c| c as u64) + } else { + s.parse().ok() } - _ => None, + .unwrap_or(0), + _ => 0, } } - pub fn get_i64(&self) -> Option { - match self { - Self::SignedInt(n) => Some(*n), - Self::Unparsed(s) => { + fn get_i64(&mut self) -> i64 { + let Some(next) = self.next() else { + return 0; + }; + match next { + FormatArgument::SignedInt(n) => *n, + FormatArgument::Unparsed(s) => { // For hex, we parse `u64` because we do not allow another // minus sign. We might need to do more precise parsing here. if let Some(s) = s.strip_prefix("-0x") { - Some(- (u64::from_str_radix(s, 16).ok()? as i64)) + u64::from_str_radix(s, 16).ok().map(|x| -(x as i64)) } else if let Some(s) = s.strip_prefix("0x") { - Some(u64::from_str_radix(s, 16).ok()? as i64) + u64::from_str_radix(s, 16).ok().map(|x| x as i64) } else if s.starts_with("-0") || s.starts_with('0') { i64::from_str_radix(s, 8).ok() } else if let Some(s) = s.strip_prefix('\'') { - Some(s.chars().next()? as i64) + s.chars().next().map(|x| x as i64) } else { s.parse().ok() } + .unwrap_or(0) } - _ => None, + _ => 0, } } - pub fn get_f64(&self) -> Option { - match self { - Self::Float(n) => Some(*n), - Self::Unparsed(s) => { - if s.starts_with("0x") || s.starts_with("-0x") { - unimplemented!("Hexadecimal floats are unimplemented!") - } else if let Some(s) = s.strip_prefix('\'') { - Some(s.chars().next()? as u64 as f64) - } else { - s.parse().ok() - } + fn get_f64(&mut self) -> f64 { + let Some(next) = self.next() else { + return 0.0; + }; + match next { + FormatArgument::Float(n) => *n, + FormatArgument::Unparsed(s) => if s.starts_with("0x") || s.starts_with("-0x") { + unimplemented!("Hexadecimal floats are unimplemented!") + } else if let Some(s) = s.strip_prefix('\'') { + s.chars().next().map(|x| x as u64 as f64) + } else { + s.parse().ok() } - _ => None, + .unwrap_or(0.0), + _ => 0.0, } } - pub fn get_str(&self) -> Option<&str> { - match self { - Self::Unparsed(s) | Self::String(s) => Some(s), - _ => None, + fn get_str(&mut self) -> &'a str { + match self.next() { + Some(FormatArgument::Unparsed(s) | FormatArgument::String(s)) => s, + _ => "", } } } diff --git a/src/uucore/src/lib/features/format/spec.rs b/src/uucore/src/lib/features/format/spec.rs index 0cd0f03b4c3..23c68c06669 100644 --- a/src/uucore/src/lib/features/format/spec.rs +++ b/src/uucore/src/lib/features/format/spec.rs @@ -5,7 +5,7 @@ use super::{ self, Case, FloatVariant, ForceDecimal, Formatter, NumberAlignment, PositiveSign, Prefix, UnsignedIntVariant, }, - parse_escape_only, FormatArgument, FormatChar, FormatError, + parse_escape_only, ArgumentIter, FormatChar, FormatError, }; use std::{fmt::Display, io::Write, ops::ControlFlow}; @@ -244,16 +244,12 @@ impl Spec { pub fn write<'a>( &self, writer: impl Write, - mut args: impl Iterator, + mut args: impl ArgumentIter<'a>, ) -> Result<(), FormatError> { match self { &Spec::Char { width, align_left } => { let width = resolve_asterisk(width, &mut args)?.unwrap_or(0); - let arg = next_arg(&mut args)?; - match arg.get_char() { - Some(c) => write_padded(writer, c, width, false, align_left), - _ => Err(FormatError::InvalidArgument(arg.clone())), - } + write_padded(writer, args.get_char(), width, false, align_left) } &Spec::String { width, @@ -263,10 +259,7 @@ impl Spec { } => { let width = resolve_asterisk(width, &mut args)?.unwrap_or(0); let precision = resolve_asterisk(precision, &mut args)?; - let arg = next_arg(&mut args)?; - let Some(s) = arg.get_str() else { - return Err(FormatError::InvalidArgument(arg.clone())); - }; + let s = args.get_str(); if parse_escape { let mut parsed = Vec::new(); for c in parse_escape_only(s.as_bytes()) { @@ -311,11 +304,7 @@ impl Spec { } => { let width = resolve_asterisk(width, &mut args)?.unwrap_or(0); let precision = resolve_asterisk(precision, &mut args)?.unwrap_or(0); - - let arg = next_arg(&mut args)?; - let Some(i) = arg.get_i64() else { - return Err(FormatError::InvalidArgument(arg.clone())); - }; + let i = args.get_i64(); num_format::SignedInt { width, @@ -334,11 +323,7 @@ impl Spec { } => { let width = resolve_asterisk(width, &mut args)?.unwrap_or(0); let precision = resolve_asterisk(precision, &mut args)?.unwrap_or(0); - - let arg = next_arg(args)?; - let Some(i) = arg.get_u64() else { - return Err(FormatError::InvalidArgument(arg.clone())); - }; + let i = args.get_u64(); num_format::UnsignedInt { variant, @@ -360,11 +345,7 @@ impl Spec { } => { let width = resolve_asterisk(width, &mut args)?.unwrap_or(0); let precision = resolve_asterisk(precision, &mut args)?.unwrap_or(6); - - let arg = next_arg(args)?; - let Some(f) = arg.get_f64() else { - return Err(FormatError::InvalidArgument(arg.clone())); - }; + let f = args.get_f64(); num_format::Float { variant, @@ -384,30 +365,15 @@ impl Spec { fn resolve_asterisk<'a>( option: Option>, - args: impl Iterator, + mut args: impl ArgumentIter<'a>, ) -> Result, FormatError> { Ok(match option { None => None, - Some(CanAsterisk::Asterisk) => { - let arg = next_arg(args)?; - match arg.get_u64() { - Some(u) => match usize::try_from(u) { - Ok(u) => Some(u), - Err(_) => return Err(FormatError::InvalidArgument(arg.clone())), - }, - _ => return Err(FormatError::InvalidArgument(arg.clone())), - } - } + Some(CanAsterisk::Asterisk) => Some(usize::try_from(args.get_u64()).ok().unwrap_or(0)), Some(CanAsterisk::Fixed(w)) => Some(w), }) } -fn next_arg<'a>( - mut arguments: impl Iterator, -) -> Result<&'a FormatArgument, FormatError> { - arguments.next().ok_or(FormatError::NoMoreArguments) -} - fn write_padded( mut writer: impl Write, text: impl Display, From 68d036c9a299282f77143728063f2c7d52b67c5b Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Mon, 20 Nov 2023 13:45:02 +0100 Subject: [PATCH 0377/2851] printf: basic support for unicode escape sequences --- src/uucore/src/lib/features/format/escape.rs | 49 ++++++++++++++------ src/uucore/src/lib/features/format/mod.rs | 15 +++--- 2 files changed, 44 insertions(+), 20 deletions(-) diff --git a/src/uucore/src/lib/features/format/escape.rs b/src/uucore/src/lib/features/format/escape.rs index b8c21741caf..1e06a8176dd 100644 --- a/src/uucore/src/lib/features/format/escape.rs +++ b/src/uucore/src/lib/features/format/escape.rs @@ -1,6 +1,7 @@ #[derive(Debug)] pub enum EscapedChar { - Char(u8), + Byte(u8), + Char(char), Backslash(u8), End, } @@ -61,6 +62,24 @@ fn parse_code(input: &mut &[u8], base: Base) -> Option { Some(ret) } +/// Parse `\uHHHH` and `\UHHHHHHHH` +// TODO: This should print warnings and possibly halt execution when it fails to parse +// TODO: If the character cannot be converted to u32, the input should be printed. +fn parse_unicode(input: &mut &[u8], digits: u8) -> Option { + let (c, rest) = input.split_first()?; + let mut ret = Base::Hex.to_digit(*c)? as u32; + *input = &rest[..]; + + for _ in 1..digits { + let (c, rest) = input.split_first()?; + let n = Base::Hex.to_digit(*c)?; + ret = ret.wrapping_mul(Base::Hex as u32).wrapping_add(n as u32); + *input = &rest[..]; + } + + char::from_u32(ret) +} + pub fn parse_escape_code(rest: &mut &[u8]) -> EscapedChar { if let [c, new_rest @ ..] = rest { // This is for the \NNN syntax for octal sequences. @@ -68,33 +87,35 @@ pub fn parse_escape_code(rest: &mut &[u8]) -> EscapedChar { // would be the \0NNN syntax. if let b'1'..=b'7' = c { if let Some(parsed) = parse_code(rest, Base::Oct) { - return EscapedChar::Char(parsed); + return EscapedChar::Byte(parsed); } } *rest = &new_rest[..]; match c { - b'\\' => EscapedChar::Char(b'\\'), - b'a' => EscapedChar::Char(b'\x07'), - b'b' => EscapedChar::Char(b'\x08'), + b'\\' => EscapedChar::Byte(b'\\'), + b'a' => EscapedChar::Byte(b'\x07'), + b'b' => EscapedChar::Byte(b'\x08'), b'c' => return EscapedChar::End, - b'e' => EscapedChar::Char(b'\x1b'), - b'f' => EscapedChar::Char(b'\x0c'), - b'n' => EscapedChar::Char(b'\n'), - b'r' => EscapedChar::Char(b'\r'), - b't' => EscapedChar::Char(b'\t'), - b'v' => EscapedChar::Char(b'\x0b'), + b'e' => EscapedChar::Byte(b'\x1b'), + b'f' => EscapedChar::Byte(b'\x0c'), + b'n' => EscapedChar::Byte(b'\n'), + b'r' => EscapedChar::Byte(b'\r'), + b't' => EscapedChar::Byte(b'\t'), + b'v' => EscapedChar::Byte(b'\x0b'), b'x' => { if let Some(c) = parse_code(rest, Base::Hex) { - EscapedChar::Char(c) + EscapedChar::Byte(c) } else { EscapedChar::Backslash(b'x') } } - b'0' => EscapedChar::Char(parse_code(rest, Base::Oct).unwrap_or(b'\0')), + b'0' => EscapedChar::Byte(parse_code(rest, Base::Oct).unwrap_or(b'\0')), + b'u' => EscapedChar::Char(parse_unicode(rest, 4).unwrap_or('\0')), + b'U' => EscapedChar::Char(parse_unicode(rest, 8).unwrap_or('\0')), c => EscapedChar::Backslash(*c), } } else { - EscapedChar::Char(b'\\') + EscapedChar::Byte(b'\\') } } diff --git a/src/uucore/src/lib/features/format/mod.rs b/src/uucore/src/lib/features/format/mod.rs index 47e6fa4cc0b..cfa9a034fd7 100644 --- a/src/uucore/src/lib/features/format/mod.rs +++ b/src/uucore/src/lib/features/format/mod.rs @@ -19,11 +19,12 @@ // spell-checker:ignore (vars) charf decf floatf intf scif strf Cninety -mod escape; mod argument; +mod escape; pub mod num_format; mod spec; +pub use argument::*; use spec::Spec; use std::{ error::Error, @@ -31,7 +32,6 @@ use std::{ io::{stdout, Write}, ops::ControlFlow, }; -pub use argument::*; use crate::error::UError; @@ -91,9 +91,12 @@ impl FormatChar for u8 { impl FormatChar for EscapedChar { fn write(&self, mut writer: impl Write) -> std::io::Result> { match self { - EscapedChar::Char(c) => { + EscapedChar::Byte(c) => { writer.write(&[*c])?; } + EscapedChar::Char(c) => { + write!(writer, "{c}")?; + } EscapedChar::Backslash(c) => { writer.write(&[b'\\', *c])?; } @@ -125,7 +128,7 @@ pub fn parse_spec_and_escape( [] => return None, [b'%', b'%', rest @ ..] => { current = rest; - Some(Ok(FormatItem::Char(EscapedChar::Char(b'%')))) + Some(Ok(FormatItem::Char(EscapedChar::Byte(b'%')))) } [b'%', rest @ ..] => { current = rest; @@ -141,7 +144,7 @@ pub fn parse_spec_and_escape( } [c, rest @ ..] => { current = rest; - Some(Ok(FormatItem::Char(EscapedChar::Char(*c)))) + Some(Ok(FormatItem::Char(EscapedChar::Byte(*c)))) } }) } @@ -179,7 +182,7 @@ fn parse_escape_only(fmt: &[u8]) -> impl Iterator + '_ { } [c, rest @ ..] => { current = rest; - Some(EscapedChar::Char(*c)) + Some(EscapedChar::Byte(*c)) } }) } From 90b61a8c5cb5768f93ef935cb6ee4c6d328ab128 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Mon, 20 Nov 2023 22:17:57 +0100 Subject: [PATCH 0378/2851] fuzzing: add a variable to state if we know it fails or not --- .github/workflows/fuzzing.yml | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/.github/workflows/fuzzing.yml b/.github/workflows/fuzzing.yml index e7a9cb1e329..cbb0574dee2 100644 --- a/.github/workflows/fuzzing.yml +++ b/.github/workflows/fuzzing.yml @@ -37,16 +37,13 @@ jobs: strategy: matrix: test-target: - [ - fuzz_date, - fuzz_test, - fuzz_expr, - fuzz_parse_glob, - fuzz_parse_size, - fuzz_parse_time, - # adding more fuzz tests here. - # e.g. fuzz_test_a, - ] + - { name: fuzz_test, should_pass: true } + # https://github.com/uutils/coreutils/issues/5311 + - { name: fuzz_date, should_pass: false } + - { name: fuzz_expr, should_pass: true } + - { name: fuzz_parse_glob, should_pass: true } + - { name: fuzz_parse_size, should_pass: true } + - { name: fuzz_parse_time, should_pass: true } steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@nightly @@ -59,16 +56,17 @@ jobs: - name: Restore Cached Corpus uses: actions/cache/restore@v3 with: - key: corpus-cache-${{ matrix.test-target }} + key: corpus-cache-${{ matrix.test-target.name }} path: | - fuzz/corpus/${{ matrix.test-target }} - - name: Run ${{ matrix.test-target }} for XX seconds + fuzz/corpus/${{ matrix.test-target.name }} + - name: Run ${{ matrix.test-target.name }} for XX seconds shell: bash + continue-on-error: ${{ !matrix.test-target.name.should_pass }} run: | - cargo +nightly fuzz run ${{ matrix.test-target }} -- -max_total_time=${{ env.RUN_FOR }} -detect_leaks=0 + cargo +nightly fuzz run ${{ matrix.test-target.name }} -- -max_total_time=${{ env.RUN_FOR }} -detect_leaks=0 - name: Save Corpus Cache uses: actions/cache/save@v3 with: - key: corpus-cache-${{ matrix.test-target }} + key: corpus-cache-${{ matrix.test-target.name }} path: | - fuzz/corpus/${{ matrix.test-target }} + fuzz/corpus/${{ matrix.test-target.name }} From 5c04283d6ee1a3fc7d4f7f88476809e760933f3c Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Mon, 20 Nov 2023 13:54:58 +0100 Subject: [PATCH 0379/2851] printf: address fmt, clippy, spelling and failing test --- src/uu/printf/src/printf.rs | 17 +- src/uu/seq/src/extendedbigdecimal.rs | 2 +- src/uu/seq/src/number.rs | 12 +- src/uu/seq/src/seq.rs | 4 +- .../src/lib/features/format/argument.rs | 65 ++- src/uucore/src/lib/features/format/escape.rs | 29 +- src/uucore/src/lib/features/format/mod.rs | 65 +-- .../src/lib/features/format/num_format.rs | 116 +++--- src/uucore/src/lib/features/format/spec.rs | 380 ++++++++++-------- 9 files changed, 379 insertions(+), 311 deletions(-) diff --git a/src/uu/printf/src/printf.rs b/src/uu/printf/src/printf.rs index 663411b8952..cfb0315cfb0 100644 --- a/src/uu/printf/src/printf.rs +++ b/src/uu/printf/src/printf.rs @@ -10,9 +10,9 @@ use std::io::stdout; use std::ops::ControlFlow; use clap::{crate_version, Arg, ArgAction, Command}; -use uucore::error::{UResult, UUsageError}; +use uucore::error::{UError, UResult, UUsageError}; use uucore::format::{parse_spec_and_escape, FormatArgument}; -use uucore::{format_usage, help_about, help_section, help_usage}; +use uucore::{format_usage, help_about, help_section, help_usage, show}; const VERSION: &str = "version"; const HELP: &str = "help"; @@ -49,10 +49,15 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { while args.peek().is_some() { for item in parse_spec_and_escape(format_string.as_ref()) { - match item?.write(stdout(), &mut args)? { - ControlFlow::Continue(()) => {} - ControlFlow::Break(()) => return Ok(()), - }; + match item { + Ok(item) => { + match item.write(stdout(), &mut args)? { + ControlFlow::Continue(()) => {} + ControlFlow::Break(()) => return Ok(()), + }; + } + Err(e) => show!(e), + } } } Ok(()) diff --git a/src/uu/seq/src/extendedbigdecimal.rs b/src/uu/seq/src/extendedbigdecimal.rs index ecd460ceb73..4f9a0415218 100644 --- a/src/uu/seq/src/extendedbigdecimal.rs +++ b/src/uu/seq/src/extendedbigdecimal.rs @@ -70,7 +70,7 @@ pub enum ExtendedBigDecimal { impl ExtendedBigDecimal { #[cfg(test)] pub fn zero() -> Self { - Self::BigDecimal(1.into()) + Self::BigDecimal(0.into()) } pub fn one() -> Self { diff --git a/src/uu/seq/src/number.rs b/src/uu/seq/src/number.rs index 4da1146eff6..182431a9210 100644 --- a/src/uu/seq/src/number.rs +++ b/src/uu/seq/src/number.rs @@ -3,12 +3,6 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. // spell-checker:ignore extendedbigdecimal extendedbigint -//! A type to represent the possible start, increment, and end values for seq. -//! -//! The [`Number`] enumeration represents the possible values for the -//! start, increment, and end values for `seq`. These may be integers, -//! floating point numbers, negative zero, etc. A [`Number`] can be -//! parsed from a string by calling [`str::parse`]. use num_traits::Zero; use crate::extendedbigdecimal::ExtendedBigDecimal; @@ -29,7 +23,11 @@ pub struct PreciseNumber { } impl PreciseNumber { - pub fn new(number: ExtendedBigDecimal, num_integral_digits: usize, num_fractional_digits: usize) -> Self { + pub fn new( + number: ExtendedBigDecimal, + num_integral_digits: usize, + num_fractional_digits: usize, + ) -> Self { Self { number, num_integral_digits, diff --git a/src/uu/seq/src/seq.rs b/src/uu/seq/src/seq.rs index a987405ce15..05338864545 100644 --- a/src/uu/seq/src/seq.rs +++ b/src/uu/seq/src/seq.rs @@ -122,7 +122,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { &options.terminator, options.equal_width, padding, - format, + &format, ); match result { Ok(_) => Ok(()), @@ -203,7 +203,7 @@ fn print_seq( terminator: &str, pad: bool, padding: usize, - format: Option>, + format: &Option>, ) -> std::io::Result<()> { let stdout = stdout(); let mut stdout = stdout.lock(); diff --git a/src/uucore/src/lib/features/format/argument.rs b/src/uucore/src/lib/features/format/argument.rs index 96cfeddf3f6..6370c4177e8 100644 --- a/src/uucore/src/lib/features/format/argument.rs +++ b/src/uucore/src/lib/features/format/argument.rs @@ -1,3 +1,7 @@ +use os_display::Quotable; + +use crate::{error::set_exit_code, show_warning}; + #[derive(Clone, Debug)] pub enum FormatArgument { Char(char), @@ -44,16 +48,25 @@ impl<'a, T: Iterator> ArgumentIter<'a> for T { }; match next { FormatArgument::UnsignedInt(n) => *n, - FormatArgument::Unparsed(s) => if let Some(s) = s.strip_prefix("0x") { - u64::from_str_radix(s, 16).ok() - } else if let Some(s) = s.strip_prefix("0") { - u64::from_str_radix(s, 8).ok() - } else if let Some(s) = s.strip_prefix('\'') { - s.chars().next().map(|c| c as u64) - } else { - s.parse().ok() + FormatArgument::Unparsed(s) => { + let opt = if let Some(s) = s.strip_prefix("0x") { + u64::from_str_radix(s, 16).ok() + } else if let Some(s) = s.strip_prefix('0') { + u64::from_str_radix(s, 8).ok() + } else if let Some(s) = s.strip_prefix('\'') { + s.chars().next().map(|c| c as u64) + } else { + s.parse().ok() + }; + match opt { + Some(n) => n, + None => { + show_warning!("{}: expected a numeric value", s.quote()); + set_exit_code(1); + 0 + } + } } - .unwrap_or(0), _ => 0, } } @@ -67,7 +80,7 @@ impl<'a, T: Iterator> ArgumentIter<'a> for T { FormatArgument::Unparsed(s) => { // For hex, we parse `u64` because we do not allow another // minus sign. We might need to do more precise parsing here. - if let Some(s) = s.strip_prefix("-0x") { + let opt = if let Some(s) = s.strip_prefix("-0x") { u64::from_str_radix(s, 16).ok().map(|x| -(x as i64)) } else if let Some(s) = s.strip_prefix("0x") { u64::from_str_radix(s, 16).ok().map(|x| x as i64) @@ -77,8 +90,15 @@ impl<'a, T: Iterator> ArgumentIter<'a> for T { s.chars().next().map(|x| x as i64) } else { s.parse().ok() + }; + match opt { + Some(n) => n, + None => { + show_warning!("{}: expected a numeric value", s.quote()); + set_exit_code(1); + 0 + } } - .unwrap_or(0) } _ => 0, } @@ -90,14 +110,23 @@ impl<'a, T: Iterator> ArgumentIter<'a> for T { }; match next { FormatArgument::Float(n) => *n, - FormatArgument::Unparsed(s) => if s.starts_with("0x") || s.starts_with("-0x") { - unimplemented!("Hexadecimal floats are unimplemented!") - } else if let Some(s) = s.strip_prefix('\'') { - s.chars().next().map(|x| x as u64 as f64) - } else { - s.parse().ok() + FormatArgument::Unparsed(s) => { + let opt = if s.starts_with("0x") || s.starts_with("-0x") { + unimplemented!("Hexadecimal floats are unimplemented!") + } else if let Some(s) = s.strip_prefix('\'') { + s.chars().next().map(|x| x as u64 as f64) + } else { + s.parse().ok() + }; + match opt { + Some(n) => n, + None => { + show_warning!("{}: expected a numeric value", s.quote()); + set_exit_code(1); + 0.0 + } + } } - .unwrap_or(0.0), _ => 0.0, } } diff --git a/src/uucore/src/lib/features/format/escape.rs b/src/uucore/src/lib/features/format/escape.rs index 1e06a8176dd..188dd1892b5 100644 --- a/src/uucore/src/lib/features/format/escape.rs +++ b/src/uucore/src/lib/features/format/escape.rs @@ -21,16 +21,16 @@ impl Base { } } - fn to_digit(&self, c: u8) -> Option { + fn convert_digit(&self, c: u8) -> Option { match self { - Base::Oct => { + Self::Oct => { if matches!(c, b'0'..=b'7') { Some(c - b'0') } else { None } } - Base::Hex => match c { + Self::Hex => match c { b'0'..=b'9' => Some(c - b'0'), b'A'..=b'F' => Some(c - b'A' + 10), b'a'..=b'f' => Some(c - b'a' + 10), @@ -49,32 +49,35 @@ fn parse_code(input: &mut &[u8], base: Base) -> Option { // yield incorrect results because it will interpret values larger than // `u8::MAX` as unicode. let [c, rest @ ..] = input else { return None }; - let mut ret = base.to_digit(*c)?; - *input = &rest[..]; + let mut ret = base.convert_digit(*c)?; + *input = rest; for _ in 1..base.max_digits() { let [c, rest @ ..] = input else { break }; - let Some(n) = base.to_digit(*c) else { break }; + let Some(n) = base.convert_digit(*c) else { + break; + }; ret = ret.wrapping_mul(base as u8).wrapping_add(n); - *input = &rest[..]; + *input = rest; } Some(ret) } +// spell-checker:disable-next /// Parse `\uHHHH` and `\UHHHHHHHH` // TODO: This should print warnings and possibly halt execution when it fails to parse // TODO: If the character cannot be converted to u32, the input should be printed. fn parse_unicode(input: &mut &[u8], digits: u8) -> Option { let (c, rest) = input.split_first()?; - let mut ret = Base::Hex.to_digit(*c)? as u32; - *input = &rest[..]; + let mut ret = Base::Hex.convert_digit(*c)? as u32; + *input = rest; for _ in 1..digits { let (c, rest) = input.split_first()?; - let n = Base::Hex.to_digit(*c)?; + let n = Base::Hex.convert_digit(*c)?; ret = ret.wrapping_mul(Base::Hex as u32).wrapping_add(n as u32); - *input = &rest[..]; + *input = rest; } char::from_u32(ret) @@ -91,12 +94,12 @@ pub fn parse_escape_code(rest: &mut &[u8]) -> EscapedChar { } } - *rest = &new_rest[..]; + *rest = new_rest; match c { b'\\' => EscapedChar::Byte(b'\\'), b'a' => EscapedChar::Byte(b'\x07'), b'b' => EscapedChar::Byte(b'\x08'), - b'c' => return EscapedChar::End, + b'c' => EscapedChar::End, b'e' => EscapedChar::Byte(b'\x1b'), b'f' => EscapedChar::Byte(b'\x0c'), b'n' => EscapedChar::Byte(b'\n'), diff --git a/src/uucore/src/lib/features/format/mod.rs b/src/uucore/src/lib/features/format/mod.rs index cfa9a034fd7..9045b8b90c3 100644 --- a/src/uucore/src/lib/features/format/mod.rs +++ b/src/uucore/src/lib/features/format/mod.rs @@ -42,10 +42,13 @@ use self::{ #[derive(Debug)] pub enum FormatError { - SpecError, + SpecError(Vec), IoError(std::io::Error), NoMoreArguments, InvalidArgument(FormatArgument), + TooManySpecs, + NeedAtLeastOneSpec, + WrongSpecType, } impl Error for FormatError {} @@ -53,18 +56,26 @@ impl UError for FormatError {} impl From for FormatError { fn from(value: std::io::Error) -> Self { - FormatError::IoError(value) + Self::IoError(value) } } impl Display for FormatError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - // TODO: Be more precise about these match self { - FormatError::SpecError => write!(f, "invalid spec"), - FormatError::IoError(_) => write!(f, "io error"), - FormatError::NoMoreArguments => write!(f, "no more arguments"), - FormatError::InvalidArgument(_) => write!(f, "invalid argument"), + Self::SpecError(s) => write!( + f, + "%{}: invalid conversion specification", + String::from_utf8_lossy(s) + ), + // TODO: The next two should print the spec as well + Self::TooManySpecs => write!(f, "format has too many % directives"), + Self::NeedAtLeastOneSpec => write!(f, "format has no % directive"), + // TODO: Error message below needs some work + Self::WrongSpecType => write!(f, "wrong % directive type was given"), + Self::IoError(_) => write!(f, "io error"), + Self::NoMoreArguments => write!(f, "no more arguments"), + Self::InvalidArgument(_) => write!(f, "invalid argument"), } } } @@ -83,7 +94,7 @@ pub trait FormatChar { impl FormatChar for u8 { fn write(&self, mut writer: impl Write) -> std::io::Result> { - writer.write(&[*self])?; + writer.write_all(&[*self])?; Ok(ControlFlow::Continue(())) } } @@ -91,16 +102,16 @@ impl FormatChar for u8 { impl FormatChar for EscapedChar { fn write(&self, mut writer: impl Write) -> std::io::Result> { match self { - EscapedChar::Byte(c) => { - writer.write(&[*c])?; + Self::Byte(c) => { + writer.write_all(&[*c])?; } - EscapedChar::Char(c) => { + Self::Char(c) => { write!(writer, "{c}")?; } - EscapedChar::Backslash(c) => { - writer.write(&[b'\\', *c])?; + Self::Backslash(c) => { + writer.write_all(&[b'\\', *c])?; } - EscapedChar::End => return Ok(ControlFlow::Break(())), + Self::End => return Ok(ControlFlow::Break(())), } Ok(ControlFlow::Continue(())) } @@ -113,8 +124,8 @@ impl FormatItem { args: &mut impl Iterator, ) -> Result, FormatError> { match self { - FormatItem::Spec(spec) => spec.write(writer, args)?, - FormatItem::Char(c) => return c.write(writer).map_err(FormatError::IoError), + Self::Spec(spec) => spec.write(writer, args)?, + Self::Char(c) => return c.write(writer).map_err(FormatError::IoError), }; Ok(ControlFlow::Continue(())) } @@ -125,7 +136,7 @@ pub fn parse_spec_and_escape( ) -> impl Iterator, FormatError>> + '_ { let mut current = fmt; std::iter::from_fn(move || match current { - [] => return None, + [] => None, [b'%', b'%', rest @ ..] => { current = rest; Some(Ok(FormatItem::Char(EscapedChar::Byte(b'%')))) @@ -133,8 +144,8 @@ pub fn parse_spec_and_escape( [b'%', rest @ ..] => { current = rest; let spec = match Spec::parse(&mut current) { - Some(spec) => spec, - None => return Some(Err(FormatError::SpecError)), + Ok(spec) => spec, + Err(slice) => return Some(Err(FormatError::SpecError(slice.to_vec()))), }; Some(Ok(FormatItem::Spec(spec))) } @@ -152,7 +163,7 @@ pub fn parse_spec_and_escape( fn parse_spec_only(fmt: &[u8]) -> impl Iterator, FormatError>> + '_ { let mut current = fmt; std::iter::from_fn(move || match current { - [] => return None, + [] => None, [b'%', b'%', rest @ ..] => { current = rest; Some(Ok(FormatItem::Char(b'%'))) @@ -160,8 +171,8 @@ fn parse_spec_only(fmt: &[u8]) -> impl Iterator, Fo [b'%', rest @ ..] => { current = rest; let spec = match Spec::parse(&mut current) { - Some(spec) => spec, - None => return Some(Err(FormatError::SpecError)), + Ok(spec) => spec, + Err(slice) => return Some(Err(FormatError::SpecError(slice.to_vec()))), }; Some(Ok(FormatItem::Spec(spec))) } @@ -175,7 +186,7 @@ fn parse_spec_only(fmt: &[u8]) -> impl Iterator, Fo fn parse_escape_only(fmt: &[u8]) -> impl Iterator + '_ { let mut current = fmt; std::iter::from_fn(move || match current { - [] => return None, + [] => None, [b'\\', rest @ ..] => { current = rest; Some(parse_escape_code(&mut current)) @@ -248,8 +259,8 @@ pub fn sprintf<'a>( /// A parsed format for a single float value /// -/// This is used by `seq`. It can be constructed with [`FloatFormat::parse`] -/// and can write a value with [`FloatFormat::fmt`]. +/// This is used by `seq`. It can be constructed with [`Format::parse`] +/// and can write a value with [`Format::fmt`]. /// /// It can only accept a single specification without any asterisk parameters. /// If it does get more specifications, it will return an error. @@ -276,7 +287,7 @@ impl Format { } let Some(spec) = spec else { - return Err(FormatError::SpecError); + return Err(FormatError::NeedAtLeastOneSpec); }; let formatter = F::try_from_spec(spec)?; @@ -285,7 +296,7 @@ impl Format { for item in &mut iter { match item? { FormatItem::Spec(_) => { - return Err(FormatError::SpecError); + return Err(FormatError::TooManySpecs); } FormatItem::Char(c) => suffix.push(c), } diff --git a/src/uucore/src/lib/features/format/num_format.rs b/src/uucore/src/lib/features/format/num_format.rs index 49edecce085..c9a2b8c166f 100644 --- a/src/uucore/src/lib/features/format/num_format.rs +++ b/src/uucore/src/lib/features/format/num_format.rs @@ -97,19 +97,19 @@ impl Formatter for SignedInt { alignment, } = s else { - return Err(FormatError::SpecError); + return Err(FormatError::WrongSpecType); }; let width = match width { Some(CanAsterisk::Fixed(x)) => x, None => 0, - Some(CanAsterisk::Asterisk) => return Err(FormatError::SpecError), + Some(CanAsterisk::Asterisk) => return Err(FormatError::WrongSpecType), }; let precision = match precision { Some(CanAsterisk::Fixed(x)) => x, None => 0, - Some(CanAsterisk::Asterisk) => return Err(FormatError::SpecError), + Some(CanAsterisk::Asterisk) => return Err(FormatError::WrongSpecType), }; Ok(Self { @@ -151,7 +151,7 @@ impl Formatter for UnsignedInt { }; if self.precision > s.len() { - s = format!("{:0width$}", s, width = self.precision) + s = format!("{:0width$}", s, width = self.precision); } match self.alignment { @@ -169,19 +169,19 @@ impl Formatter for UnsignedInt { alignment, } = s else { - return Err(FormatError::SpecError); + return Err(FormatError::WrongSpecType); }; let width = match width { Some(CanAsterisk::Fixed(x)) => x, None => 0, - Some(CanAsterisk::Asterisk) => return Err(FormatError::SpecError), + Some(CanAsterisk::Asterisk) => return Err(FormatError::WrongSpecType), }; let precision = match precision { Some(CanAsterisk::Fixed(x)) => x, None => 0, - Some(CanAsterisk::Asterisk) => return Err(FormatError::SpecError), + Some(CanAsterisk::Asterisk) => return Err(FormatError::WrongSpecType), }; Ok(Self { @@ -212,7 +212,7 @@ impl Default for Float { width: 0, positive_sign: PositiveSign::None, alignment: NumberAlignment::Left, - precision: 2, + precision: 6, } } } @@ -229,19 +229,23 @@ impl Formatter for Float { }?; } - let s = match self.variant { - FloatVariant::Decimal => { - format_float_decimal(x, self.precision, self.case, self.force_decimal) - } - FloatVariant::Scientific => { - format_float_scientific(x, self.precision, self.case, self.force_decimal) - } - FloatVariant::Shortest => { - format_float_shortest(x, self.precision, self.case, self.force_decimal) - } - FloatVariant::Hexadecimal => { - format_float_hexadecimal(x, self.precision, self.case, self.force_decimal) + let s = if x.is_finite() { + match self.variant { + FloatVariant::Decimal => { + format_float_decimal(x, self.precision, self.force_decimal) + } + FloatVariant::Scientific => { + format_float_scientific(x, self.precision, self.case, self.force_decimal) + } + FloatVariant::Shortest => { + format_float_shortest(x, self.precision, self.case, self.force_decimal) + } + FloatVariant::Hexadecimal => { + format_float_hexadecimal(x, self.precision, self.case, self.force_decimal) + } } + } else { + format_float_non_finite(x, self.case) }; match self.alignment { @@ -265,19 +269,19 @@ impl Formatter for Float { precision, } = s else { - return Err(FormatError::SpecError); + return Err(FormatError::WrongSpecType); }; let width = match width { Some(CanAsterisk::Fixed(x)) => x, None => 0, - Some(CanAsterisk::Asterisk) => return Err(FormatError::SpecError), + Some(CanAsterisk::Asterisk) => return Err(FormatError::WrongSpecType), }; let precision = match precision { Some(CanAsterisk::Fixed(x)) => x, None => 0, - Some(CanAsterisk::Asterisk) => return Err(FormatError::SpecError), + Some(CanAsterisk::Asterisk) => return Err(FormatError::WrongSpecType), }; Ok(Self { @@ -292,25 +296,16 @@ impl Formatter for Float { } } -fn format_float_nonfinite(f: f64, case: Case) -> String { +fn format_float_non_finite(f: f64, case: Case) -> String { debug_assert!(!f.is_finite()); let mut s = format!("{f}"); if case == Case::Uppercase { s.make_ascii_uppercase(); } - return s; + s } -fn format_float_decimal( - f: f64, - precision: usize, - case: Case, - force_decimal: ForceDecimal, -) -> String { - if !f.is_finite() { - return format_float_nonfinite(f, case); - } - +fn format_float_decimal(f: f64, precision: usize, force_decimal: ForceDecimal) -> String { if precision == 0 && force_decimal == ForceDecimal::Yes { format!("{f:.0}.") } else { @@ -324,11 +319,6 @@ fn format_float_scientific( case: Case, force_decimal: ForceDecimal, ) -> String { - // If the float is NaN, -Nan, Inf or -Inf, format like any other float - if !f.is_finite() { - return format_float_nonfinite(f, case); - } - if f == 0.0 { return if force_decimal == ForceDecimal::Yes && precision == 0 { "0.e+00".into() @@ -337,13 +327,13 @@ fn format_float_scientific( }; } - let mut exponent: i32 = f.log10().floor() as i32; let mut normalized = f / 10.0_f64.powi(exponent); // If the normalized value will be rounded to a value greater than 10 // we need to correct. - if (normalized * 10_f64.powi(precision as i32)).round() / 10_f64.powi(precision as i32) >= 10.0 { + if (normalized * 10_f64.powi(precision as i32)).round() / 10_f64.powi(precision as i32) >= 10.0 + { normalized /= 10.0; exponent += 1; } @@ -371,11 +361,6 @@ fn format_float_shortest( case: Case, force_decimal: ForceDecimal, ) -> String { - // If the float is NaN, -Nan, Inf or -Inf, format like any other float - if !f.is_finite() { - return format_float_nonfinite(f, case); - } - // Precision here is about how many digits should be displayed // instead of how many digits for the fractional part, this means that if // we pass this to rust's format string, it's always gonna be one less. @@ -398,7 +383,9 @@ fn format_float_shortest( // If the normalized value will be rounded to a value greater than 10 // we need to correct. - if (normalized * 10_f64.powi(precision as i32)).round() / 10_f64.powi(precision as i32) >= 10.0 { + if (normalized * 10_f64.powi(precision as i32)).round() / 10_f64.powi(precision as i32) + >= 10.0 + { normalized /= 10.0; exponent += 1; } @@ -412,12 +399,7 @@ fn format_float_shortest( let mut normalized = format!("{normalized:.*}", precision); if force_decimal == ForceDecimal::No { - while normalized.ends_with('0') { - normalized.pop(); - } - if normalized.ends_with('.') { - normalized.pop(); - } + strip_zeros_and_dot(&mut normalized); } let exp_char = match case { @@ -439,12 +421,7 @@ fn format_float_shortest( }; if force_decimal == ForceDecimal::No { - while formatted.ends_with('0') { - formatted.pop(); - } - if formatted.ends_with('.') { - formatted.pop(); - } + strip_zeros_and_dot(&mut formatted); } formatted @@ -457,10 +434,6 @@ fn format_float_hexadecimal( case: Case, force_decimal: ForceDecimal, ) -> String { - if !f.is_finite() { - return format_float_nonfinite(f, case); - } - let (first_digit, mantissa, exponent) = if f == 0.0 { (0, 0, 0) } else { @@ -481,7 +454,16 @@ fn format_float_hexadecimal( s.make_ascii_uppercase(); } - return s; + s +} + +fn strip_zeros_and_dot(s: &mut String) { + while s.ends_with('0') { + s.pop(); + } + if s.ends_with('.') { + s.pop(); + } } #[cfg(test)] @@ -491,7 +473,7 @@ mod test { #[test] fn decimal_float() { use super::format_float_decimal; - let f = |x| format_float_decimal(x, 6, Case::Lowercase, ForceDecimal::No); + let f = |x| format_float_decimal(x, 6, ForceDecimal::No); assert_eq!(f(0.0), "0.000000"); assert_eq!(f(1.0), "1.000000"); assert_eq!(f(100.0), "100.000000"); @@ -576,7 +558,7 @@ mod test { assert_eq!(f(12.3456789), "1e+01"); assert_eq!(f(1000000.0), "1e+06"); assert_eq!(f(99999999.0), "1e+08"); - + let f = |x| format_float_shortest(x, 0, Case::Lowercase, ForceDecimal::Yes); assert_eq!(f(0.0), "0."); assert_eq!(f(1.0), "1."); diff --git a/src/uucore/src/lib/features/format/spec.rs b/src/uucore/src/lib/features/format/spec.rs index 23c68c06669..e74b6f8660f 100644 --- a/src/uucore/src/lib/features/format/spec.rs +++ b/src/uucore/src/lib/features/format/spec.rs @@ -1,4 +1,6 @@ -// spell-checker:ignore (vars) charf decf floatf intf scif strf Cninety +// spell-checker:ignore (vars) charf decf floatf intf scif strf Cninety intmax ptrdiff + +use crate::quoting_style::{escape_name, QuotingStyle}; use super::{ num_format::{ @@ -16,11 +18,12 @@ pub enum Spec { align_left: bool, }, String { - width: Option>, precision: Option>, - parse_escape: bool, + width: Option>, align_left: bool, }, + EscapedString, + QuotedString, SignedInt { width: Option>, precision: Option>, @@ -76,12 +79,14 @@ enum Length { } impl Spec { - pub fn parse(rest: &mut &[u8]) -> Option { + pub fn parse<'a>(rest: &mut &'a [u8]) -> Result { // Based on the C++ reference, the spec format looks like: // // %[flags][width][.precision][length]specifier // // However, we have already parsed the '%'. + let mut index = 0; + let start = *rest; let mut minus = false; let mut plus = false; @@ -89,111 +94,101 @@ impl Spec { let mut hash = false; let mut zero = false; - while let Some(x @ (b'-' | b'+' | b' ' | b'#' | b'0')) = rest.get(0) { + while let Some(x) = rest.get(index) { match x { b'-' => minus = true, b'+' => plus = true, b' ' => space = true, b'#' => hash = true, b'0' => zero = true, - _ => unreachable!(), + _ => break, } - *rest = &rest[1..] + index += 1; } - let width = eat_asterisk_or_number(rest); + let alignment = match (minus, zero) { + (true, _) => NumberAlignment::Left, + (false, true) => NumberAlignment::RightZero, + (false, false) => NumberAlignment::RightSpace, + }; - let precision = if let Some(b'.') = rest.get(0) { - *rest = &rest[1..]; - Some(eat_asterisk_or_number(rest).unwrap_or(CanAsterisk::Fixed(0))) + let positive_sign = match (plus, space) { + (true, _) => PositiveSign::Plus, + (false, true) => PositiveSign::Space, + (false, false) => PositiveSign::None, + }; + + let width = eat_asterisk_or_number(rest, &mut index); + + let precision = if let Some(b'.') = rest.get(index) { + index += 1; + Some(eat_asterisk_or_number(rest, &mut index).unwrap_or(CanAsterisk::Fixed(0))) } else { None }; - // Parse 0..N length options, keep the last one - // Even though it is just ignored. We might want to use it later and we - // should parse those characters. - // - // TODO: This needs to be configurable: `seq` accepts only one length - // param - let mut _length = None; - loop { - let new_length = rest.get(0).and_then(|c| { - Some(match c { - b'h' => { - if let Some(b'h') = rest.get(1) { - *rest = &rest[1..]; - Length::Char - } else { - Length::Short - } - } - b'l' => { - if let Some(b'l') = rest.get(1) { - *rest = &rest[1..]; - Length::Long - } else { - Length::LongLong - } - } - b'j' => Length::IntMaxT, - b'z' => Length::SizeT, - b't' => Length::PtfDiffT, - b'L' => Length::LongDouble, - _ => return None, - }) - }); - if new_length.is_some() { - *rest = &rest[1..]; - _length = new_length; - } else { - break; - } - } + // We ignore the length. It's not really relevant to printf + let _ = Self::parse_length(rest, &mut index); - let type_spec = rest.get(0)?; - *rest = &rest[1..]; - Some(match type_spec { - b'c' => Spec::Char { - width, - align_left: minus, - }, - b's' => Spec::String { - width, - precision, - parse_escape: false, - align_left: minus, - }, - b'b' => Spec::String { - width, - precision, - parse_escape: true, - align_left: minus, - }, - b'd' | b'i' => Spec::SignedInt { - width, - precision, - alignment: match (minus, zero) { - (true, _) => NumberAlignment::Left, - (false, true) => NumberAlignment::RightZero, - (false, false) => NumberAlignment::RightSpace, - }, - positive_sign: match (plus, space) { - (true, _) => PositiveSign::Plus, - (false, true) => PositiveSign::Space, - (false, false) => PositiveSign::None, - }, - }, + let Some(type_spec) = rest.get(index) else { + return Err(&start[..index]); + }; + index += 1; + *rest = &start[index..]; + + Ok(match type_spec { + // GNU accepts minus, plus and space even though they are not used + b'c' => { + if hash || precision.is_some() { + return Err(&start[..index]); + } + Self::Char { + width, + align_left: minus, + } + } + b's' => { + if hash { + return Err(&start[..index]); + } + Self::String { + precision, + width, + align_left: minus, + } + } + b'b' => { + if hash || minus || plus || space || width.is_some() || precision.is_some() { + return Err(&start[..index]); + } + Self::EscapedString + } + b'q' => { + if hash || minus || plus || space || width.is_some() || precision.is_some() { + return Err(&start[..index]); + } + Self::QuotedString + } + b'd' | b'i' => { + if hash { + return Err(&start[..index]); + } + Self::SignedInt { + width, + precision, + alignment, + positive_sign, + } + } c @ (b'u' | b'o' | b'x' | b'X') => { + // Normal unsigned integer cannot have a prefix + if *c == b'u' && hash { + return Err(&start[..index]); + } let prefix = match hash { false => Prefix::No, true => Prefix::Yes, }; - let alignment = match (minus, zero) { - (true, _) => NumberAlignment::Left, - (false, true) => NumberAlignment::RightZero, - (false, false) => NumberAlignment::RightSpace, - }; let variant = match c { b'u' => UnsignedIntVariant::Decimal, b'o' => UnsignedIntVariant::Octal(prefix), @@ -201,14 +196,14 @@ impl Spec { b'X' => UnsignedIntVariant::Hexadecimal(Case::Uppercase, prefix), _ => unreachable!(), }; - Spec::UnsignedInt { + Self::UnsignedInt { variant, precision, width, alignment, } } - c @ (b'f' | b'F' | b'e' | b'E' | b'g' | b'G' | b'a' | b'A') => Spec::Float { + c @ (b'f' | b'F' | b'e' | b'E' | b'g' | b'G' | b'a' | b'A') => Self::Float { width, precision, variant: match c { @@ -226,115 +221,157 @@ impl Spec { false => Case::Lowercase, true => Case::Uppercase, }, - alignment: match (minus, zero) { - (true, _) => NumberAlignment::Left, - (false, true) => NumberAlignment::RightZero, - (false, false) => NumberAlignment::RightSpace, - }, - positive_sign: match (plus, space) { - (true, _) => PositiveSign::Plus, - (false, true) => PositiveSign::Space, - (false, false) => PositiveSign::None, - }, + alignment, + positive_sign, }, - _ => return None, + _ => return Err(&start[..index]), }) } + fn parse_length(rest: &mut &[u8], index: &mut usize) -> Option { + // Parse 0..N length options, keep the last one + // Even though it is just ignored. We might want to use it later and we + // should parse those characters. + // + // TODO: This needs to be configurable: `seq` accepts only one length + // param + let mut length = None; + loop { + let new_length = rest.get(*index).and_then(|c| { + Some(match c { + b'h' => { + if let Some(b'h') = rest.get(*index + 1) { + *index += 1; + Length::Char + } else { + Length::Short + } + } + b'l' => { + if let Some(b'l') = rest.get(*index + 1) { + *index += 1; + Length::Long + } else { + Length::LongLong + } + } + b'j' => Length::IntMaxT, + b'z' => Length::SizeT, + b't' => Length::PtfDiffT, + b'L' => Length::LongDouble, + _ => return None, + }) + }); + if new_length.is_some() { + *index += 1; + length = new_length; + } else { + break; + } + } + length + } + pub fn write<'a>( &self, - writer: impl Write, + mut writer: impl Write, mut args: impl ArgumentIter<'a>, ) -> Result<(), FormatError> { match self { - &Spec::Char { width, align_left } => { - let width = resolve_asterisk(width, &mut args)?.unwrap_or(0); - write_padded(writer, args.get_char(), width, false, align_left) + Self::Char { width, align_left } => { + let width = resolve_asterisk(*width, &mut args)?.unwrap_or(0); + write_padded(writer, args.get_char(), width, false, *align_left) } - &Spec::String { + Self::String { width, - precision, - parse_escape, align_left, + precision, } => { - let width = resolve_asterisk(width, &mut args)?.unwrap_or(0); - let precision = resolve_asterisk(precision, &mut args)?; + let width = resolve_asterisk(*width, &mut args)?.unwrap_or(0); + + // GNU does do this truncation on a byte level, see for instance: + // printf "%.1s" 🙃 + // > � + // For now, we let printf panic when we truncate within a code point. + // TODO: We need to not use Rust's formatting for aligning the output, + // so that we can just write bytes to stdout without panicking. + let precision = resolve_asterisk(*precision, &mut args)?; let s = args.get_str(); - if parse_escape { - let mut parsed = Vec::new(); - for c in parse_escape_only(s.as_bytes()) { - match c.write(&mut parsed)? { - ControlFlow::Continue(()) => {} - ControlFlow::Break(()) => { - // TODO: This should break the _entire execution_ of printf - break; - } - }; - } - // GNU does do this truncation on a byte level, see for instance: - // printf "%.1s" 🙃 - // > � - // For now, we let printf panic when we truncate within a code point. - // TODO: We need to not use Rust's formatting for aligning the output, - // so that we can just write bytes to stdout without panicking. - let truncated = match precision { - Some(p) if p < parsed.len() => &parsed[..p], - _ => &parsed, - }; - write_padded( - writer, - std::str::from_utf8(&truncated).expect("TODO: Accept invalid utf8"), - width, - false, - align_left, - ) - } else { - let truncated = match precision { - Some(p) if p < s.len() => &s[..p], - _ => s, + let truncated = match precision { + Some(p) if p < s.len() => &s[..p], + _ => s, + }; + write_padded(writer, truncated, width, false, *align_left) + } + Self::EscapedString => { + let s = args.get_str(); + let mut parsed = Vec::new(); + for c in parse_escape_only(s.as_bytes()) { + match c.write(&mut parsed)? { + ControlFlow::Continue(()) => {} + ControlFlow::Break(()) => { + // TODO: This should break the _entire execution_ of printf + break; + } }; - write_padded(writer, truncated, width, false, align_left) } + writer.write_all(&parsed).map_err(FormatError::IoError) + } + Self::QuotedString => { + let s = args.get_str(); + writer + .write_all( + escape_name( + s.as_ref(), + &QuotingStyle::Shell { + escape: true, + always_quote: false, + show_control: false, + }, + ) + .as_bytes(), + ) + .map_err(FormatError::IoError) } - &Spec::SignedInt { + Self::SignedInt { width, precision, positive_sign, alignment, } => { - let width = resolve_asterisk(width, &mut args)?.unwrap_or(0); - let precision = resolve_asterisk(precision, &mut args)?.unwrap_or(0); + let width = resolve_asterisk(*width, &mut args)?.unwrap_or(0); + let precision = resolve_asterisk(*precision, &mut args)?.unwrap_or(0); let i = args.get_i64(); num_format::SignedInt { width, precision, - positive_sign, - alignment, + positive_sign: *positive_sign, + alignment: *alignment, } .fmt(writer, i) .map_err(FormatError::IoError) } - &Spec::UnsignedInt { + Self::UnsignedInt { variant, width, precision, alignment, } => { - let width = resolve_asterisk(width, &mut args)?.unwrap_or(0); - let precision = resolve_asterisk(precision, &mut args)?.unwrap_or(0); + let width = resolve_asterisk(*width, &mut args)?.unwrap_or(0); + let precision = resolve_asterisk(*precision, &mut args)?.unwrap_or(0); let i = args.get_u64(); num_format::UnsignedInt { - variant, + variant: *variant, precision, width, - alignment, + alignment: *alignment, } .fmt(writer, i) .map_err(FormatError::IoError) } - &Spec::Float { + Self::Float { variant, case, force_decimal, @@ -343,18 +380,18 @@ impl Spec { alignment, precision, } => { - let width = resolve_asterisk(width, &mut args)?.unwrap_or(0); - let precision = resolve_asterisk(precision, &mut args)?.unwrap_or(6); + let width = resolve_asterisk(*width, &mut args)?.unwrap_or(0); + let precision = resolve_asterisk(*precision, &mut args)?.unwrap_or(6); let f = args.get_f64(); num_format::Float { - variant, - case, - force_decimal, width, - positive_sign, - alignment, precision, + variant: *variant, + case: *case, + force_decimal: *force_decimal, + positive_sign: *positive_sign, + alignment: *alignment, } .fmt(writer, f) .map_err(FormatError::IoError) @@ -390,23 +427,26 @@ fn write_padded( .map_err(FormatError::IoError) } -fn eat_asterisk_or_number(rest: &mut &[u8]) -> Option> { - if let Some(b'*') = rest.get(0) { - *rest = &rest[1..]; +fn eat_asterisk_or_number(rest: &mut &[u8], index: &mut usize) -> Option> { + if let Some(b'*') = rest.get(*index) { + *index += 1; Some(CanAsterisk::Asterisk) } else { - eat_number(rest).map(CanAsterisk::Fixed) + eat_number(rest, index).map(CanAsterisk::Fixed) } } -fn eat_number(rest: &mut &[u8]) -> Option { - match rest.iter().position(|b| !b.is_ascii_digit()) { +fn eat_number(rest: &mut &[u8], index: &mut usize) -> Option { + match rest[*index..].iter().position(|b| !b.is_ascii_digit()) { None | Some(0) => None, Some(i) => { // TODO: This might need to handle errors better // For example in case of overflow. - let parsed = std::str::from_utf8(&rest[..i]).unwrap().parse().unwrap(); - *rest = &rest[i..]; + let parsed = std::str::from_utf8(&rest[*index..(*index + i)]) + .unwrap() + .parse() + .unwrap(); + *index += i; Some(parsed) } } From 131c310bcbd0c437f55ca0b45677b3d66be8c4ce Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Tue, 21 Nov 2023 08:49:19 +0100 Subject: [PATCH 0380/2851] Cargo.toml: default_features -> default-features --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 2f3af2c83d1..ba701b2d5e5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -330,7 +330,7 @@ walkdir = "2.4" winapi-util = "0.1.6" windows-sys = { version = "0.48.0", default-features = false } xattr = "1.0.1" -zip = { version = "0.6.6", default_features = false, features = ["deflate"] } +zip = { version = "0.6.6", default-features = false, features = ["deflate"] } hex = "0.4.3" md-5 = "0.10.6" From d2ede927367d87a79901cf45e4fee2dfa1c8f7dc Mon Sep 17 00:00:00 2001 From: ALXD Date: Mon, 20 Nov 2023 14:19:20 +0100 Subject: [PATCH 0381/2851] expr: make error messages align with GNU expr --- src/uu/expr/src/syntax_tree.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/uu/expr/src/syntax_tree.rs b/src/uu/expr/src/syntax_tree.rs index c55fb0bdc6a..b19c13c0adf 100644 --- a/src/uu/expr/src/syntax_tree.rs +++ b/src/uu/expr/src/syntax_tree.rs @@ -13,6 +13,7 @@ use num_bigint::BigInt; use num_traits::Zero; use onig::{Regex, RegexOptions, Syntax}; +use uucore::display::Quotable; use crate::tokens::Token; @@ -214,7 +215,7 @@ pub fn tokens_to_ast( assert!(op_stack.is_empty()); maybe_dump_rpn(&out_stack); - let result = ast_from_rpn(&mut out_stack); + let result = ast_from_rpn(&mut out_stack, None); if out_stack.is_empty() { maybe_dump_ast(&result); result @@ -253,9 +254,12 @@ fn maybe_dump_rpn(rpn: &TokenStack) { } } -fn ast_from_rpn(rpn: &mut TokenStack) -> Result, String> { +fn ast_from_rpn(rpn: &mut TokenStack, op_type: Option<&str>) -> Result, String> { match rpn.pop() { - None => Err("syntax error (premature end of expression)".to_owned()), + None => Err(match op_type { + Some(value) => format!("syntax error: unexpected argument {}", value.quote()), + None => "missing operand".to_owned(), + }), Some((token_idx, Token::Value { value })) => Ok(AstNode::new_leaf(token_idx, &value)), @@ -281,7 +285,7 @@ fn maybe_ast_node( ) -> Result, String> { let mut operands = Vec::with_capacity(arity); for _ in 0..arity { - let operand = ast_from_rpn(rpn)?; + let operand = ast_from_rpn(rpn, Some(op_type))?; operands.push(operand); } operands.reverse(); From 8b650a7a9b5c6356425c37fa0ef49c28c49aea76 Mon Sep 17 00:00:00 2001 From: ALXD Date: Mon, 20 Nov 2023 14:29:06 +0100 Subject: [PATCH 0382/2851] expr: add tests for precise error messages --- tests/by-util/test_expr.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/by-util/test_expr.rs b/tests/by-util/test_expr.rs index 28cfcf0ec90..18125fe99dd 100644 --- a/tests/by-util/test_expr.rs +++ b/tests/by-util/test_expr.rs @@ -6,6 +6,14 @@ use crate::common::util::TestScenario; +#[test] +fn test_no_arguments() { + new_ucmd!() + .fails() + .code_is(2) + .stderr_only("expr: missing operand\n"); +} + #[test] fn test_simple_values() { // null or 0 => EXIT_VALUE == 1 @@ -275,6 +283,12 @@ fn test_substr() { #[test] fn test_invalid_substr() { + new_ucmd!() + .args(&["56", "substr"]) + .fails() + .code_is(2) + .stderr_only("expr: syntax error: unexpected argument 'substr'\n"); + new_ucmd!() .args(&["substr", "abc", "0", "1"]) .fails() From a0ac3dd22975ceadfb42498d62b216cc264f0a86 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Tue, 21 Nov 2023 12:38:12 +0100 Subject: [PATCH 0383/2851] fuzz printf (#5556) Co-authored-by: Daniel Hofstetter --- .github/workflows/fuzzing.yml | 1 + fuzz/Cargo.toml | 7 ++ fuzz/fuzz_targets/fuzz_printf.rs | 110 +++++++++++++++++++++++++++++++ 3 files changed, 118 insertions(+) create mode 100644 fuzz/fuzz_targets/fuzz_printf.rs diff --git a/.github/workflows/fuzzing.yml b/.github/workflows/fuzzing.yml index cbb0574dee2..2274f6905c1 100644 --- a/.github/workflows/fuzzing.yml +++ b/.github/workflows/fuzzing.yml @@ -41,6 +41,7 @@ jobs: # https://github.com/uutils/coreutils/issues/5311 - { name: fuzz_date, should_pass: false } - { name: fuzz_expr, should_pass: true } + - { name: fuzz_printf, should_pass: false } - { name: fuzz_parse_glob, should_pass: true } - { name: fuzz_parse_size, should_pass: true } - { name: fuzz_parse_time, should_pass: true } diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index 549f9a6b762..630af4650b6 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -16,6 +16,7 @@ uucore = { path = "../src/uucore/" } uu_date = { path = "../src/uu/date/" } uu_test = { path = "../src/uu/test/" } uu_expr = { path = "../src/uu/expr/" } +uu_printf = { path = "../src/uu/printf/" } # Prevent this from interfering with workspaces @@ -28,6 +29,12 @@ path = "fuzz_targets/fuzz_date.rs" test = false doc = false +[[bin]] +name = "fuzz_printf" +path = "fuzz_targets/fuzz_printf.rs" +test = false +doc = false + [[bin]] name = "fuzz_expr" path = "fuzz_targets/fuzz_expr.rs" diff --git a/fuzz/fuzz_targets/fuzz_printf.rs b/fuzz/fuzz_targets/fuzz_printf.rs new file mode 100644 index 00000000000..78bb3e3ce3b --- /dev/null +++ b/fuzz/fuzz_targets/fuzz_printf.rs @@ -0,0 +1,110 @@ +// This file is part of the uutils coreutils package. +// +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. +// spell-checker:ignore parens + +#![no_main] +use libfuzzer_sys::fuzz_target; +use uu_printf::uumain; + +use rand::seq::SliceRandom; +use rand::Rng; +use std::ffi::OsString; + +mod fuzz_common; +use crate::fuzz_common::CommandResult; +use crate::fuzz_common::{ + compare_result, generate_and_run_uumain, generate_random_string, run_gnu_cmd, +}; + +static CMD_PATH: &str = "printf"; + +fn generate_escape_sequence(rng: &mut impl Rng) -> String { + let escape_sequences = [ + "\\\"", + "\\\\", + "\\a", + "\\b", + "\\c", + "\\e", + "\\f", + "\\n", + "\\r", + "\\t", + "\\v", + "\\000", + "\\x00", + "\\u0000", + "\\U00000000", + "%%", + ]; + escape_sequences.choose(rng).unwrap().to_string() +} + +fn generate_printf() -> String { + let mut rng = rand::thread_rng(); + let format_specifiers = ["%s", "%d", "%f", "%x", "%o", "%c", "%b", "%q"]; + let mut printf_str = String::new(); + // Add a 20% chance of generating an invalid format specifier + if rng.gen_bool(0.2) { + printf_str.push_str("%z"); // Invalid format specifier + } else { + let specifier = *format_specifiers.choose(&mut rng).unwrap(); + printf_str.push_str(specifier); + + // Add a 20% chance of introducing complex format strings + if rng.gen_bool(0.2) { + printf_str.push_str(&format!(" %{}", rng.gen_range(1..=1000))); + } else { + // Add a random string or number after the specifier + if specifier == "%s" { + printf_str.push_str(&format!( + " {}", + generate_random_string(rng.gen_range(1..=10)) + )); + } else { + printf_str.push_str(&format!(" {}", rng.gen_range(1..=1000))); + } + } + } + + // Add a 10% chance of including an escape sequence + if rng.gen_bool(0.1) { + printf_str.push_str(&generate_escape_sequence(&mut rng)); + } + printf_str +} + +fuzz_target!(|_data: &[u8]| { + let printf_input = generate_printf(); + let mut args = vec![OsString::from("printf")]; + args.extend(printf_input.split_whitespace().map(OsString::from)); + let rust_result = generate_and_run_uumain(&args, uumain); + + let gnu_result = match run_gnu_cmd(CMD_PATH, &args[1..], false) { + Ok(result) => result, + Err(error_result) => { + eprintln!("Failed to run GNU command:"); + eprintln!("Stderr: {}", error_result.stderr); + eprintln!("Exit Code: {}", error_result.exit_code); + CommandResult { + stdout: String::new(), + stderr: error_result.stderr, + exit_code: error_result.exit_code, + } + } + }; + + compare_result( + "printf", + &format!("{:?}", &args[1..]), + &rust_result.stdout, + &gnu_result.stdout, + &rust_result.stderr, + &gnu_result.stderr, + rust_result.exit_code, + gnu_result.exit_code, + false, // Set to true if you want to fail on stderr diff + ); +}); From da7c168f9d7c27e09c582a99e6dba9c68955c3ec Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Mon, 20 Nov 2023 17:46:47 +0100 Subject: [PATCH 0384/2851] all: reduce imports needed for show and show_if_err macros --- src/uu/cut/src/cut.rs | 2 +- src/uu/dd/src/dd.rs | 4 ++-- src/uu/head/src/head.rs | 2 +- src/uu/mkdir/src/mkdir.rs | 2 +- src/uu/mv/src/mv.rs | 2 +- src/uu/numfmt/src/numfmt.rs | 2 +- src/uu/realpath/src/realpath.rs | 4 ++-- src/uu/shred/src/shred.rs | 2 +- src/uu/tail/src/tail.rs | 2 +- src/uu/touch/src/touch.rs | 2 +- src/uucore/src/lib/macros.rs | 3 ++- 11 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/uu/cut/src/cut.rs b/src/uu/cut/src/cut.rs index 05e8bc6e424..1a2a8ea01e9 100644 --- a/src/uu/cut/src/cut.rs +++ b/src/uu/cut/src/cut.rs @@ -17,7 +17,7 @@ use uucore::line_ending::LineEnding; use self::searcher::Searcher; use matcher::{ExactMatcher, Matcher, WhitespaceMatcher}; use uucore::ranges::Range; -use uucore::{format_usage, help_about, help_section, help_usage, show, show_error, show_if_err}; +use uucore::{format_usage, help_about, help_section, help_usage, show_error, show_if_err}; mod matcher; mod searcher; diff --git a/src/uu/dd/src/dd.rs b/src/uu/dd/src/dd.rs index b79ae22da4e..2472fba9b0b 100644 --- a/src/uu/dd/src/dd.rs +++ b/src/uu/dd/src/dd.rs @@ -52,9 +52,9 @@ use uucore::display::Quotable; #[cfg(unix)] use uucore::error::set_exit_code; use uucore::error::{FromIo, UResult}; -use uucore::{format_usage, help_about, help_section, help_usage, show_error}; #[cfg(target_os = "linux")] -use uucore::{show, show_if_err}; +use uucore::show_if_err; +use uucore::{format_usage, help_about, help_section, help_usage, show_error}; const ABOUT: &str = help_about!("dd.md"); const AFTER_HELP: &str = help_section!("after help", "dd.md"); diff --git a/src/uu/head/src/head.rs b/src/uu/head/src/head.rs index 5d0d3beddc9..83bdd37390a 100644 --- a/src/uu/head/src/head.rs +++ b/src/uu/head/src/head.rs @@ -9,7 +9,7 @@ use clap::{crate_version, Arg, ArgAction, ArgMatches, Command}; use std::ffi::OsString; use std::io::{self, BufWriter, ErrorKind, Read, Seek, SeekFrom, Write}; use uucore::display::Quotable; -use uucore::error::{FromIo, UError, UResult, USimpleError}; +use uucore::error::{FromIo, UResult, USimpleError}; use uucore::line_ending::LineEnding; use uucore::lines::lines; use uucore::{format_usage, help_about, help_usage, show}; diff --git a/src/uu/mkdir/src/mkdir.rs b/src/uu/mkdir/src/mkdir.rs index 4121278b69a..c29905ef4a7 100644 --- a/src/uu/mkdir/src/mkdir.rs +++ b/src/uu/mkdir/src/mkdir.rs @@ -16,7 +16,7 @@ use uucore::error::{UResult, USimpleError}; #[cfg(not(windows))] use uucore::mode; use uucore::{display::Quotable, fs::dir_strip_dot_for_creation}; -use uucore::{format_usage, help_about, help_section, help_usage, show, show_if_err}; +use uucore::{format_usage, help_about, help_section, help_usage, show_if_err}; static DEFAULT_PERM: u32 = 0o777; diff --git a/src/uu/mv/src/mv.rs b/src/uu/mv/src/mv.rs index 036024f9907..60dabf34582 100644 --- a/src/uu/mv/src/mv.rs +++ b/src/uu/mv/src/mv.rs @@ -21,7 +21,7 @@ use std::os::windows; use std::path::{Path, PathBuf}; use uucore::backup_control::{self, source_is_target_backup}; use uucore::display::Quotable; -use uucore::error::{set_exit_code, FromIo, UError, UResult, USimpleError, UUsageError}; +use uucore::error::{set_exit_code, FromIo, UResult, USimpleError, UUsageError}; use uucore::fs::{are_hardlinks_or_one_way_symlink_to_same_file, are_hardlinks_to_same_file}; use uucore::update_control; // These are exposed for projects (e.g. nushell) that want to create an `Options` value, which diff --git a/src/uu/numfmt/src/numfmt.rs b/src/uu/numfmt/src/numfmt.rs index d1785209d06..d158072fbb4 100644 --- a/src/uu/numfmt/src/numfmt.rs +++ b/src/uu/numfmt/src/numfmt.rs @@ -13,7 +13,7 @@ use std::str::FromStr; use units::{IEC_BASES, SI_BASES}; use uucore::display::Quotable; -use uucore::error::{UError, UResult}; +use uucore::error::UResult; use uucore::ranges::Range; use uucore::{format_usage, help_about, help_section, help_usage, show, show_error}; diff --git a/src/uu/realpath/src/realpath.rs b/src/uu/realpath/src/realpath.rs index 64806fbabf6..b099a5f377c 100644 --- a/src/uu/realpath/src/realpath.rs +++ b/src/uu/realpath/src/realpath.rs @@ -15,13 +15,13 @@ use std::{ use uucore::fs::make_path_relative_to; use uucore::{ display::{print_verbatim, Quotable}, - error::{FromIo, UResult}, + error::{FromIo, UClapError, UResult}, format_usage, fs::{canonicalize, MissingHandling, ResolveMode}, help_about, help_usage, line_ending::LineEnding, + show_if_err, }; -use uucore::{error::UClapError, show, show_if_err}; static ABOUT: &str = help_about!("realpath.md"); const USAGE: &str = help_usage!("realpath.md"); diff --git a/src/uu/shred/src/shred.rs b/src/uu/shred/src/shred.rs index eb63f0e5f2e..04f81bf23fd 100644 --- a/src/uu/shred/src/shred.rs +++ b/src/uu/shred/src/shred.rs @@ -17,7 +17,7 @@ use std::path::{Path, PathBuf}; use uucore::display::Quotable; use uucore::error::{FromIo, UResult, USimpleError, UUsageError}; use uucore::parse_size::parse_size_u64; -use uucore::{format_usage, help_about, help_section, help_usage, show, show_error, show_if_err}; +use uucore::{format_usage, help_about, help_section, help_usage, show_error, show_if_err}; const ABOUT: &str = help_about!("shred.md"); const USAGE: &str = help_usage!("shred.md"); diff --git a/src/uu/tail/src/tail.rs b/src/uu/tail/src/tail.rs index 0488e0808ed..edac4b151cb 100644 --- a/src/uu/tail/src/tail.rs +++ b/src/uu/tail/src/tail.rs @@ -31,7 +31,7 @@ use std::fs::File; use std::io::{self, stdin, stdout, BufRead, BufReader, BufWriter, Read, Seek, SeekFrom, Write}; use std::path::{Path, PathBuf}; use uucore::display::Quotable; -use uucore::error::{get_exit_code, set_exit_code, FromIo, UError, UResult, USimpleError}; +use uucore::error::{get_exit_code, set_exit_code, FromIo, UResult, USimpleError}; use uucore::{show, show_error}; #[uucore::main] diff --git a/src/uu/touch/src/touch.rs b/src/uu/touch/src/touch.rs index d9399a051f6..51c09801b0a 100644 --- a/src/uu/touch/src/touch.rs +++ b/src/uu/touch/src/touch.rs @@ -17,7 +17,7 @@ use std::ffi::OsString; use std::fs::{self, File}; use std::path::{Path, PathBuf}; use uucore::display::Quotable; -use uucore::error::{FromIo, UError, UResult, USimpleError}; +use uucore::error::{FromIo, UResult, USimpleError}; use uucore::{format_usage, help_about, help_usage, show}; const ABOUT: &str = help_about!("touch.md"); diff --git a/src/uucore/src/lib/macros.rs b/src/uucore/src/lib/macros.rs index ad86d530828..d1a09c281ab 100644 --- a/src/uucore/src/lib/macros.rs +++ b/src/uucore/src/lib/macros.rs @@ -91,6 +91,7 @@ pub static UTILITY_IS_SECOND_ARG: AtomicBool = AtomicBool::new(false); #[macro_export] macro_rules! show( ($err:expr) => ({ + use $crate::error::UError; let e = $err; $crate::error::set_exit_code(e.code()); eprintln!("{}: {}", $crate::util_name(), e); @@ -131,7 +132,7 @@ macro_rules! show( macro_rules! show_if_err( ($res:expr) => ({ if let Err(e) = $res { - show!(e); + $crate::show!(e); } }) ); From 17d21d2d9c7c421202cd692e427ab13bfd01bf60 Mon Sep 17 00:00:00 2001 From: Zhuoxun Yang Date: Tue, 21 Nov 2023 22:05:41 +0800 Subject: [PATCH 0385/2851] expr: check prefix operation --- src/uu/expr/src/syntax_tree.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/uu/expr/src/syntax_tree.rs b/src/uu/expr/src/syntax_tree.rs index c55fb0bdc6a..119f325a3d1 100644 --- a/src/uu/expr/src/syntax_tree.rs +++ b/src/uu/expr/src/syntax_tree.rs @@ -332,8 +332,12 @@ fn push_token_to_either_stack( } Token::PrefixOp { .. } | Token::ParOpen => { - op_stack.push((token_idx, token.clone())); - Ok(()) + if out_stack.is_empty() { + op_stack.push((token_idx, token.clone())); + Ok(()) + } else { + Err(String::from("syntax error (operation should be prefix)")) + } } Token::ParClose => move_till_match_paren(out_stack, op_stack), From 44702940d2437a4427638f94870f57cde5d5f8c0 Mon Sep 17 00:00:00 2001 From: Zhuoxun Yang Date: Tue, 21 Nov 2023 22:06:20 +0800 Subject: [PATCH 0386/2851] tests/expr: check prefix operation --- tests/by-util/test_expr.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tests/by-util/test_expr.rs b/tests/by-util/test_expr.rs index 28cfcf0ec90..8582082c483 100644 --- a/tests/by-util/test_expr.rs +++ b/tests/by-util/test_expr.rs @@ -100,6 +100,11 @@ fn test_parenthesis() { .args(&["(", "1", "+", "1", ")", "*", "2"]) .succeeds() .stdout_only("4\n"); + + new_ucmd!() + .args(&["1", "(", ")"]) + .fails() + .stderr_only("expr: syntax error (operation should be prefix)\n"); } #[test] @@ -221,6 +226,11 @@ fn test_index() { .args(&["index", "αbcdef_f", "f"]) .succeeds() .stdout_only("6\n"); + + new_ucmd!() + .args(&["αbcdef", "index", "α"]) + .fails() + .stderr_only("expr: syntax error (operation should be prefix)\n"); } #[test] @@ -234,6 +244,11 @@ fn test_length() { .args(&["length", "abcdef"]) .succeeds() .stdout_only("6\n"); + + new_ucmd!() + .args(&["abcdef", "length"]) + .fails() + .stderr_only("expr: syntax error (operation should be prefix)\n"); } #[test] @@ -271,6 +286,11 @@ fn test_substr() { .args(&["substr", "abc", "1", "1"]) .succeeds() .stdout_only("a\n"); + + new_ucmd!() + .args(&["abc", "substr", "1", "1"]) + .fails() + .stderr_only("expr: syntax error (operation should be prefix)\n"); } #[test] From 0822511fdcd00d95554601983c2d2e72becaa2ce Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Tue, 21 Nov 2023 16:49:20 +0100 Subject: [PATCH 0387/2851] test/printf: ignoring rounding up to 2 This is a limitation of the current implementation, which should ultimately use "long double" precision instead of f64. --- tests/by-util/test_printf.rs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/tests/by-util/test_printf.rs b/tests/by-util/test_printf.rs index 875896a9f02..dfd13159043 100644 --- a/tests/by-util/test_printf.rs +++ b/tests/by-util/test_printf.rs @@ -296,9 +296,18 @@ fn sub_num_float_e_no_round() { } #[test] -fn sub_num_float_round() { +fn sub_num_float_round_to_one() { new_ucmd!() - .args(&["two is %f", "1.9999996"]) + .args(&["one is %f", "0.9999995"]) + .succeeds() + .stdout_only("one is 1.000000"); +} + +#[test] +#[ignore = "Requires 'long double' precision floats to be used internally"] +fn sub_num_float_round_to_two() { + new_ucmd!() + .args(&["two is %f", "1.9999995"]) .succeeds() .stdout_only("two is 2.000000"); } From 18b5c22567f76fdafd138d31bf1dcaf3c3cce7c2 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 21 Nov 2023 22:24:11 +0000 Subject: [PATCH 0388/2851] fix(deps): update rust crate data-encoding to 2.5 --- Cargo.lock | 4 ++-- src/uucore/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c89c87df41c..3cf5dbcc551 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -690,9 +690,9 @@ checksum = "ef8ae57c4978a2acd8b869ce6b9ca1dfe817bff704c220209fdef2c0b75a01b9" [[package]] name = "data-encoding" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308" +checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5" [[package]] name = "data-encoding-macro" diff --git a/src/uucore/Cargo.toml b/src/uucore/Cargo.toml index 370c8a3864c..cbe34d5f7c8 100644 --- a/src/uucore/Cargo.toml +++ b/src/uucore/Cargo.toml @@ -33,7 +33,7 @@ time = { workspace = true, optional = true, features = [ "macros", ] } # * "problem" dependencies (pinned) -data-encoding = { version = "2.4", optional = true } +data-encoding = { version = "2.5", optional = true } data-encoding-macro = { version = "0.1.13", optional = true } z85 = { version = "3.0.5", optional = true } libc = { workspace = true, optional = true } From 3425ee8d5957e0ad75d9b7fad873ca56ac3d2721 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 22 Nov 2023 06:09:44 +0000 Subject: [PATCH 0389/2851] fix(deps): update rust crate data-encoding-macro to 0.1.14 --- Cargo.lock | 8 ++++---- src/uucore/Cargo.toml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3cf5dbcc551..d7682f839e4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -696,9 +696,9 @@ checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5" [[package]] name = "data-encoding-macro" -version = "0.1.13" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c904b33cc60130e1aeea4956ab803d08a3f4a0ca82d64ed757afac3891f2bb99" +checksum = "20c01c06f5f429efdf2bae21eb67c28b3df3cf85b7dd2d8ef09c0838dac5d33e" dependencies = [ "data-encoding", "data-encoding-macro-internal", @@ -706,9 +706,9 @@ dependencies = [ [[package]] name = "data-encoding-macro-internal" -version = "0.1.11" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fdf3fce3ce863539ec1d7fd1b6dcc3c645663376b43ed376bbf887733e4f772" +checksum = "0047d07f2c89b17dd631c80450d69841a6b5d7fb17278cbc43d7e4cfcf2576f3" dependencies = [ "data-encoding", "syn 1.0.109", diff --git a/src/uucore/Cargo.toml b/src/uucore/Cargo.toml index cbe34d5f7c8..fabf068bb4d 100644 --- a/src/uucore/Cargo.toml +++ b/src/uucore/Cargo.toml @@ -34,7 +34,7 @@ time = { workspace = true, optional = true, features = [ ] } # * "problem" dependencies (pinned) data-encoding = { version = "2.5", optional = true } -data-encoding-macro = { version = "0.1.13", optional = true } +data-encoding-macro = { version = "0.1.14", optional = true } z85 = { version = "3.0.5", optional = true } libc = { workspace = true, optional = true } once_cell = { workspace = true } From e95add794066612aa007f2c7e49855ec30693c4d Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Wed, 22 Nov 2023 12:38:10 +0100 Subject: [PATCH 0390/2851] uucore/format: fix license headers and improve docs --- .../src/lib/features/format/argument.rs | 12 +++++ src/uucore/src/lib/features/format/escape.rs | 11 +++++ src/uucore/src/lib/features/format/mod.rs | 46 +++++++++++++------ .../src/lib/features/format/num_format.rs | 7 +++ src/uucore/src/lib/features/format/spec.rs | 11 ++++- 5 files changed, 71 insertions(+), 16 deletions(-) diff --git a/src/uucore/src/lib/features/format/argument.rs b/src/uucore/src/lib/features/format/argument.rs index 6370c4177e8..db18cf51890 100644 --- a/src/uucore/src/lib/features/format/argument.rs +++ b/src/uucore/src/lib/features/format/argument.rs @@ -1,7 +1,19 @@ +// This file is part of the uutils coreutils package. +// +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. + use os_display::Quotable; use crate::{error::set_exit_code, show_warning}; +/// An argument for formatting +/// +/// Each of these variants is only accepted by their respective directives. For +/// example, [`FormatArgument::Char`] requires a `%c` directive. +/// +/// The [`FormatArgument::Unparsed`] variant contains a string that can be +/// parsed into other types. This is used by the `printf` utility. #[derive(Clone, Debug)] pub enum FormatArgument { Char(char), diff --git a/src/uucore/src/lib/features/format/escape.rs b/src/uucore/src/lib/features/format/escape.rs index 188dd1892b5..d20da3e7e38 100644 --- a/src/uucore/src/lib/features/format/escape.rs +++ b/src/uucore/src/lib/features/format/escape.rs @@ -1,8 +1,19 @@ +// This file is part of the uutils coreutils package. +// +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. + +//! Parsing of escape sequences + #[derive(Debug)] pub enum EscapedChar { + /// A single byte Byte(u8), + /// A unicode character Char(char), + /// A character prefixed with a backslash (i.e. an invalid escape sequence) Backslash(u8), + /// Specifies that the string should stop (`\c`) End, } diff --git a/src/uucore/src/lib/features/format/mod.rs b/src/uucore/src/lib/features/format/mod.rs index 9045b8b90c3..d213d0359cf 100644 --- a/src/uucore/src/lib/features/format/mod.rs +++ b/src/uucore/src/lib/features/format/mod.rs @@ -1,23 +1,34 @@ -//! Main entry point for our implementation of printf. +// This file is part of the uutils coreutils package. +// +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. + +//! `printf`-style formatting +//! +//! Rust has excellent formatting capabilities, but the coreutils require very +//! specific formatting that needs to work exactly like the GNU utilities. +//! Naturally, the GNU behavior is based on the C `printf` functionality. //! -//! The [`printf`] and [`sprintf`] closely match the behavior of the +//! Additionally, we need support for escape sequences for the `printf` utility. +//! +//! The [`printf`] and [`sprintf`] functions closely match the behavior of the //! corresponding C functions: the former renders a formatted string //! to stdout, the latter renders to a new [`String`] object. //! -//! In addition to the [`printf`] and [`sprintf`] functions, we expose the -//! [`Format`] struct, which represents a parsed format string. This reduces -//! the need for parsing a format string multiple times and assures that no -//! parsing errors occur during writing. -//! //! There are three kinds of parsing that we might want to do: //! -//! 1. Only `printf` specifiers (for e.g. `seq`, `dd`) -//! 2. Only escape sequences (for e.g. `echo`) -//! 3. Both `printf` specifiers and escape sequences (for e.g. `printf`) +//! 1. Parse only `printf` directives (for e.g. `seq`, `dd`) +//! 2. Parse only escape sequences (for e.g. `echo`) +//! 3. Parse both `printf` specifiers and escape sequences (for e.g. `printf`) //! -//! This module aims to combine all three use cases. - -// spell-checker:ignore (vars) charf decf floatf intf scif strf Cninety +//! This module aims to combine all three use cases. An iterator parsing each +//! of these cases is provided by [`parse_escape_only`], [`parse_spec_only`] +//! and [`parse_spec_and_escape`], respectively. +//! +//! There is a special [`Format`] type, which can be used to parse a format +//! string containing exactly one directive and does not use any `*` in that +//! directive. This format can be printed in a type-safe manner without failing +//! (modulo IO errors). mod argument; mod escape; @@ -131,6 +142,7 @@ impl FormatItem { } } +/// Parse a format string containing % directives and escape sequences pub fn parse_spec_and_escape( fmt: &[u8], ) -> impl Iterator, FormatError>> + '_ { @@ -160,7 +172,10 @@ pub fn parse_spec_and_escape( }) } -fn parse_spec_only(fmt: &[u8]) -> impl Iterator, FormatError>> + '_ { +/// Parse a format string containing % directives +pub fn parse_spec_only( + fmt: &[u8], +) -> impl Iterator, FormatError>> + '_ { let mut current = fmt; std::iter::from_fn(move || match current { [] => None, @@ -183,7 +198,8 @@ fn parse_spec_only(fmt: &[u8]) -> impl Iterator, Fo }) } -fn parse_escape_only(fmt: &[u8]) -> impl Iterator + '_ { +/// Parse a format string containing escape sequences +pub fn parse_escape_only(fmt: &[u8]) -> impl Iterator + '_ { let mut current = fmt; std::iter::from_fn(move || match current { [] => None, diff --git a/src/uucore/src/lib/features/format/num_format.rs b/src/uucore/src/lib/features/format/num_format.rs index c9a2b8c166f..6fd177d1325 100644 --- a/src/uucore/src/lib/features/format/num_format.rs +++ b/src/uucore/src/lib/features/format/num_format.rs @@ -1,3 +1,10 @@ +// This file is part of the uutils coreutils package. +// +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. + +//! Utilities for formatting numbers in various formats + use std::io::Write; use super::{ diff --git a/src/uucore/src/lib/features/format/spec.rs b/src/uucore/src/lib/features/format/spec.rs index e74b6f8660f..7c0d0236764 100644 --- a/src/uucore/src/lib/features/format/spec.rs +++ b/src/uucore/src/lib/features/format/spec.rs @@ -1,4 +1,9 @@ -// spell-checker:ignore (vars) charf decf floatf intf scif strf Cninety intmax ptrdiff +// This file is part of the uutils coreutils package. +// +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. + +// spell-checker:ignore (vars) intmax ptrdiff use crate::quoting_style::{escape_name, QuotingStyle}; @@ -11,6 +16,10 @@ use super::{ }; use std::{fmt::Display, io::Write, ops::ControlFlow}; +/// A parsed specification for formatting a value +/// +/// This might require more than one argument to resolve width or precision +/// values that are given as `*`. #[derive(Debug)] pub enum Spec { Char { From 2e77d99dd4258a853e172a96ed5349d6bd2e169b Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Thu, 23 Nov 2023 14:35:02 +0100 Subject: [PATCH 0391/2851] expr: fail fast if there are no operands --- src/uu/expr/src/expr.rs | 6 +++++- tests/by-util/test_expr.rs | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/uu/expr/src/expr.rs b/src/uu/expr/src/expr.rs index ea559090c92..909c4c37653 100644 --- a/src/uu/expr/src/expr.rs +++ b/src/uu/expr/src/expr.rs @@ -5,7 +5,7 @@ use clap::{crate_version, Arg, ArgAction, Command}; use uucore::{ - error::{UResult, USimpleError}, + error::{UResult, USimpleError, UUsageError}, format_usage, help_about, help_section, help_usage, }; @@ -58,6 +58,10 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { .map(|v| v.into_iter().map(|s| s.as_ref()).collect::>()) .unwrap_or_default(); + if token_strings.is_empty() { + return Err(UUsageError::new(2, "missing operand")); + } + match process_expr(&token_strings[..]) { Ok(expr_result) => print_expr_ok(&expr_result), Err(expr_error) => Err(USimpleError::new(2, &expr_error)), diff --git a/tests/by-util/test_expr.rs b/tests/by-util/test_expr.rs index 72d7687b7d1..41fc8d4540b 100644 --- a/tests/by-util/test_expr.rs +++ b/tests/by-util/test_expr.rs @@ -11,7 +11,7 @@ fn test_no_arguments() { new_ucmd!() .fails() .code_is(2) - .stderr_only("expr: missing operand\n"); + .usage_error("missing operand"); } #[test] From c2bfb6a465aac1dce51cc04510820893fad0c1dd Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Thu, 23 Nov 2023 15:03:53 +0100 Subject: [PATCH 0392/2851] expr: adapt error messages, revert most of #5559 --- src/uu/expr/src/syntax_tree.rs | 28 ++++++++++++++++++---------- tests/by-util/test_expr.rs | 18 ++++++++---------- 2 files changed, 26 insertions(+), 20 deletions(-) diff --git a/src/uu/expr/src/syntax_tree.rs b/src/uu/expr/src/syntax_tree.rs index 89c23e41249..0654f2ac331 100644 --- a/src/uu/expr/src/syntax_tree.rs +++ b/src/uu/expr/src/syntax_tree.rs @@ -215,7 +215,7 @@ pub fn tokens_to_ast( assert!(op_stack.is_empty()); maybe_dump_rpn(&out_stack); - let result = ast_from_rpn(&mut out_stack, None); + let result = ast_from_rpn(&mut out_stack); if out_stack.is_empty() { maybe_dump_ast(&result); result @@ -254,13 +254,9 @@ fn maybe_dump_rpn(rpn: &TokenStack) { } } -fn ast_from_rpn(rpn: &mut TokenStack, op_type: Option<&str>) -> Result, String> { +fn ast_from_rpn(rpn: &mut TokenStack) -> Result, String> { match rpn.pop() { - None => Err(match op_type { - Some(value) => format!("syntax error: unexpected argument {}", value.quote()), - None => "missing operand".to_owned(), - }), - + None => Err("syntax error (premature end of expression)".to_owned()), Some((token_idx, Token::Value { value })) => Ok(AstNode::new_leaf(token_idx, &value)), Some((token_idx, Token::InfixOp { value, .. })) => { @@ -285,7 +281,7 @@ fn maybe_ast_node( ) -> Result, String> { let mut operands = Vec::with_capacity(arity); for _ in 0..arity { - let operand = ast_from_rpn(rpn, Some(op_type))?; + let operand = ast_from_rpn(rpn)?; operands.push(operand); } operands.reverse(); @@ -335,12 +331,24 @@ fn push_token_to_either_stack( } } - Token::PrefixOp { .. } | Token::ParOpen => { + Token::ParOpen => { if out_stack.is_empty() { op_stack.push((token_idx, token.clone())); Ok(()) } else { - Err(String::from("syntax error (operation should be prefix)")) + Err("syntax error: unexpected argument '('".to_string()) + } + } + + Token::PrefixOp { value, .. } => { + if out_stack.is_empty() { + op_stack.push((token_idx, token.clone())); + Ok(()) + } else { + Err(format!( + "syntax error: unexpected argument {}", + value.quote() + )) } } diff --git a/tests/by-util/test_expr.rs b/tests/by-util/test_expr.rs index 41fc8d4540b..f29752f66c9 100644 --- a/tests/by-util/test_expr.rs +++ b/tests/by-util/test_expr.rs @@ -112,7 +112,8 @@ fn test_parenthesis() { new_ucmd!() .args(&["1", "(", ")"]) .fails() - .stderr_only("expr: syntax error (operation should be prefix)\n"); + .code_is(2) + .stderr_only("expr: syntax error: unexpected argument '('\n"); } #[test] @@ -238,7 +239,8 @@ fn test_index() { new_ucmd!() .args(&["αbcdef", "index", "α"]) .fails() - .stderr_only("expr: syntax error (operation should be prefix)\n"); + .code_is(2) + .stderr_only("expr: syntax error: unexpected argument 'index'\n"); } #[test] @@ -256,7 +258,8 @@ fn test_length() { new_ucmd!() .args(&["abcdef", "length"]) .fails() - .stderr_only("expr: syntax error (operation should be prefix)\n"); + .code_is(2) + .stderr_only("expr: syntax error: unexpected argument 'length'\n"); } #[test] @@ -298,17 +301,12 @@ fn test_substr() { new_ucmd!() .args(&["abc", "substr", "1", "1"]) .fails() - .stderr_only("expr: syntax error (operation should be prefix)\n"); + .code_is(2) + .stderr_only("expr: syntax error: unexpected argument 'substr'\n"); } #[test] fn test_invalid_substr() { - new_ucmd!() - .args(&["56", "substr"]) - .fails() - .code_is(2) - .stderr_only("expr: syntax error: unexpected argument 'substr'\n"); - new_ucmd!() .args(&["substr", "abc", "0", "1"]) .fails() From 7efe33108a8bebada31e962cfb8c0d5a70b07efe Mon Sep 17 00:00:00 2001 From: Coba Weel <122735+cobaweel@users.noreply.github.com> Date: Wed, 22 Nov 2023 16:15:59 -0800 Subject: [PATCH 0393/2851] Fix issue 5576 (regex matching bug in expr) Issue 5576 reported a bug in expr, found by the fuzzer. The problem turns out to be with the regex match operator `:`, which is defined in POSIX and the GNU manual to match the pattern only when it occurs at the beginning of the string, i.e., the regex has an implicit `^` prepended to it. We hadn't been doing that. --- src/uu/expr/src/syntax_tree.rs | 3 ++- tests/by-util/test_expr.rs | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/uu/expr/src/syntax_tree.rs b/src/uu/expr/src/syntax_tree.rs index 0654f2ac331..2260b2e2186 100644 --- a/src/uu/expr/src/syntax_tree.rs +++ b/src/uu/expr/src/syntax_tree.rs @@ -498,7 +498,8 @@ fn infix_operator_and(values: &[String]) -> String { fn operator_match(values: &[String]) -> Result { assert!(values.len() == 2); - let re = Regex::with_options(&values[1], RegexOptions::REGEX_OPTION_NONE, Syntax::grep()) + let re_string = format!("^{}", &values[1]); + let re = Regex::with_options(&re_string, RegexOptions::REGEX_OPTION_NONE, Syntax::grep()) .map_err(|err| err.description().to_string())?; Ok(if re.captures_len() > 0 { re.captures(&values[0]) diff --git a/tests/by-util/test_expr.rs b/tests/by-util/test_expr.rs index f29752f66c9..ebc2c832feb 100644 --- a/tests/by-util/test_expr.rs +++ b/tests/by-util/test_expr.rs @@ -289,6 +289,10 @@ fn test_regex() { .args(&["-5", ":", "-\\{0,1\\}[0-9]*$"]) .succeeds() .stdout_only("2\n"); + new_ucmd!() + .args(&["abc", ":", "bc"]) + .fails() + .stdout_only("0\n"); } #[test] From 550f3b0c488451bbe9f930e0520d558f01e3c891 Mon Sep 17 00:00:00 2001 From: zoze0 Date: Fri, 24 Nov 2023 00:41:11 +0800 Subject: [PATCH 0394/2851] uucore: add support for loongarch64 (#5574) * uucore: add support for loongarch64 * add loongarch --------- Co-authored-by: Sylvestre Ledru --- .vscode/cspell.dictionaries/acronyms+names.wordlist.txt | 1 + src/uucore/src/lib/features/fs.rs | 2 ++ 2 files changed, 3 insertions(+) diff --git a/.vscode/cspell.dictionaries/acronyms+names.wordlist.txt b/.vscode/cspell.dictionaries/acronyms+names.wordlist.txt index c004ea2f822..4a59ed094bd 100644 --- a/.vscode/cspell.dictionaries/acronyms+names.wordlist.txt +++ b/.vscode/cspell.dictionaries/acronyms+names.wordlist.txt @@ -37,6 +37,7 @@ aarch flac impls lzma +loongarch # * names BusyBox diff --git a/src/uucore/src/lib/features/fs.rs b/src/uucore/src/lib/features/fs.rs index f8593dfede5..de4c0b08dbe 100644 --- a/src/uucore/src/lib/features/fs.rs +++ b/src/uucore/src/lib/features/fs.rs @@ -119,6 +119,7 @@ impl FileInformation { not(target_os = "solaris"), not(target_arch = "aarch64"), not(target_arch = "riscv64"), + not(target_arch = "loongarch64"), target_pointer_width = "64" ))] return self.0.st_nlink; @@ -133,6 +134,7 @@ impl FileInformation { target_os = "solaris", target_arch = "aarch64", target_arch = "riscv64", + target_arch = "loongarch64", not(target_pointer_width = "64") ) ))] From 4dc46f10e9f3636ad8ac68b579b73461b8035bee Mon Sep 17 00:00:00 2001 From: zhitkoff Date: Tue, 21 Nov 2023 11:04:38 -0500 Subject: [PATCH 0395/2851] split: pass GNU test l-chunk --- src/uu/split/src/split.rs | 188 ++++++++++++++++++---------- src/uu/split/src/strategy.rs | 9 +- tests/by-util/test_split.rs | 233 +++++++++++++++++++---------------- 3 files changed, 259 insertions(+), 171 deletions(-) diff --git a/src/uu/split/src/split.rs b/src/uu/split/src/split.rs index 592e4eedde9..4e2af0be4d8 100644 --- a/src/uu/split/src/split.rs +++ b/src/uu/split/src/split.rs @@ -1130,14 +1130,68 @@ impl<'a> Write for LineBytesChunkWriter<'a> { } } +/// Output file parameters +struct OutFile { + filename: String, + maybe_writer: Option>>, +} + +impl OutFile { + /// Get the writer for the output file + /// Instantiate the writer if it has not been instantiated upfront + fn get_writer(&mut self, settings: &Settings) -> UResult<&mut BufWriter>> { + if self.maybe_writer.is_some() { + Ok(self.maybe_writer.as_mut().unwrap()) + } else { + // Writer was not instantiated upfront + // Instantiate it and record for future use + self.maybe_writer = Some(settings.instantiate_current_writer(self.filename.as_str())?); + Ok(self.maybe_writer.as_mut().unwrap()) + } + } +} + +/// Generate a set of Output Files +/// This is a helper function to [`n_chunks_by_byte`], [`n_chunks_by_line`] +/// and [`n_chunks_by_line_round_robin`]. +/// Each OutFile is generated with filename, while the writer for it could be +/// optional, to be instantiated later by the calling function as needed. +/// Optional writers could happen in [`n_chunks_by_line`] +/// if `elide_empty_files` parameter is set to `true`. +fn get_out_files( + num_files: u64, + settings: &Settings, + is_writer_optional: bool, +) -> UResult> { + // This object is responsible for creating the filename for each chunk + let mut filename_iterator: FilenameIterator<'_> = + FilenameIterator::new(&settings.prefix, &settings.suffix) + .map_err(|e| io::Error::new(ErrorKind::Other, format!("{e}")))?; + let mut out_files: Vec = Vec::new(); + for _ in 0..num_files { + let filename = filename_iterator + .next() + .ok_or_else(|| USimpleError::new(1, "output file suffixes exhausted"))?; + let maybe_writer = if is_writer_optional { + None + } else { + Some(settings.instantiate_current_writer(filename.as_str())?) + }; + out_files.push(OutFile { + filename, + maybe_writer, + }); + } + Ok(out_files) +} + /// Split a file or STDIN into a specific number of chunks by byte. -/// If in Kth chunk of N mode - print the k-th chunk to STDOUT. /// /// When file size cannot be evenly divided into the number of chunks of the same size, /// the first X chunks are 1 byte longer than the rest, /// where X is a modulus reminder of (file size % number of chunks) /// -/// In Kth chunk of N mode - writes to stdout the contents of the chunk identified by `kth_chunk` +/// In Kth chunk of N mode - writes to STDOUT the contents of the chunk identified by `kth_chunk` /// /// In N chunks mode - this function always creates one output file for each chunk, even /// if there is an error reading or writing one of the chunks or if @@ -1207,7 +1261,7 @@ where // In Kth chunk of N mode - we will write to stdout instead of to a file. let mut stdout_writer = std::io::stdout().lock(); // In N chunks mode - we will write to `num_chunks` files - let mut writers = vec![]; + let mut out_files: Vec = Vec::new(); // Calculate chunk size base and modulo reminder // to be used in calculating chunk_size later on @@ -1219,16 +1273,7 @@ where // This will create each of the underlying files // or stdin pipes to child shell/command processes if in `--filter` mode if kth_chunk.is_none() { - // This object is responsible for creating the filename for each chunk. - let mut filename_iterator = FilenameIterator::new(&settings.prefix, &settings.suffix) - .map_err(|e| io::Error::new(ErrorKind::Other, format!("{e}")))?; - for _ in 0..num_chunks { - let filename = filename_iterator - .next() - .ok_or_else(|| USimpleError::new(1, "output file suffixes exhausted"))?; - let writer = settings.instantiate_current_writer(filename.as_str())?; - writers.push(writer); - } + out_files = get_out_files(num_chunks, settings, false)?; } for i in 1_u64..=num_chunks { @@ -1272,7 +1317,7 @@ where } None => { let idx = (i - 1) as usize; - let writer = writers.get_mut(idx).unwrap(); + let writer = out_files[idx].get_writer(settings)?; writer.write_all(buf)?; } } @@ -1284,9 +1329,14 @@ where } /// Split a file or STDIN into a specific number of chunks by line. -/// If in Kth chunk of N mode - print the k-th chunk to STDOUT. /// -/// In Kth chunk of N mode - writes to stdout the contents of the chunk identified by `kth_chunk` +/// It is most likely that input cannot be evenly divided into the number of chunks +/// of the same size in bytes or number of lines, since we cannot break lines. +/// It is also likely that there could be empty files (having `elide_empty_files` is disabled) +/// when a long line overlaps one or more chunks. +/// +/// In Kth chunk of N mode - writes to STDOUT the contents of the chunk identified by `kth_chunk` +/// Note: the `elide_empty_files` flag is ignored in this mode /// /// In N chunks mode - this function always creates one output file for each chunk, even /// if there is an error reading or writing one of the chunks or if @@ -1322,76 +1372,97 @@ where let initial_buf = &mut Vec::new(); let num_bytes = get_input_size(&settings.input, reader, initial_buf, &settings.io_blksize)?; let reader = initial_buf.chain(reader); - let chunk_size = (num_bytes / num_chunks) as usize; // If input file is empty and we would not have determined the Kth chunk // in the Kth chunk of N chunk mode, then terminate immediately. // This happens on `split -n l/3/10 /dev/null`, for example. - if kth_chunk.is_some() && num_bytes == 0 { + // Similarly, if input file is empty and `elide_empty_files` parameter is enabled, + // then we would have written zero chunks of output, + // so terminate immediately as well. + // This happens on `split -e -n l/3 /dev/null`, for example. + if num_bytes == 0 && (kth_chunk.is_some() || settings.elide_empty_files) { return Ok(()); } // In Kth chunk of N mode - we will write to stdout instead of to a file. let mut stdout_writer = std::io::stdout().lock(); // In N chunks mode - we will write to `num_chunks` files - let mut writers = vec![]; + let mut out_files: Vec = Vec::new(); + + // Calculate chunk size base and modulo reminder + // to be used in calculating `num_bytes_should_be_written` later on + let chunk_size_base = num_bytes / num_chunks; + let chunk_size_reminder = num_bytes % num_chunks; // If in N chunks mode - // Create one writer for each chunk. - // This will create each of the underlying files - // or stdin pipes to child shell/command processes if in `--filter` mode + // Generate filenames for each file and + // if `elide_empty_files` parameter is NOT enabled - instantiate the writer + // which will create each of the underlying files or stdin pipes + // to child shell/command processes if in `--filter` mode. + // Otherwise keep writer optional, to be instantiated later if there is data + // to write for the associated chunk. if kth_chunk.is_none() { - // This object is responsible for creating the filename for each chunk. - let mut filename_iterator = FilenameIterator::new(&settings.prefix, &settings.suffix) - .map_err(|e| io::Error::new(ErrorKind::Other, format!("{e}")))?; - for _ in 0..num_chunks { - let filename = filename_iterator - .next() - .ok_or_else(|| USimpleError::new(1, "output file suffixes exhausted"))?; - let writer = settings.instantiate_current_writer(filename.as_str())?; - writers.push(writer); - } + out_files = get_out_files(num_chunks, settings, settings.elide_empty_files)?; } - let mut num_bytes_remaining_in_current_chunk = chunk_size; - let mut i = 1; + let mut chunk_number = 1; let sep = settings.separator; + let mut num_bytes_should_be_written = chunk_size_base + (chunk_size_reminder > 0) as u64; + let mut num_bytes_written = 0; for line_result in reader.split(sep) { - // add separator back in at the end of the line let mut line = line_result?; - line.push(sep); + // add separator back in at the end of the line, + // since `reader.split(sep)` removes it, + // except if the last line did not end with separator character + if (num_bytes_written + line.len() as u64) < num_bytes { + line.push(sep); + } let bytes = line.as_slice(); match kth_chunk { - Some(chunk_number) => { - if i == chunk_number { + Some(kth) => { + if chunk_number == kth { stdout_writer.write_all(bytes)?; } } None => { - let idx = (i - 1) as usize; - let maybe_writer = writers.get_mut(idx); - let writer = maybe_writer.unwrap(); + // Should write into a file + let idx = (chunk_number - 1) as usize; + let writer = out_files[idx].get_writer(settings)?; custom_write_all(bytes, writer, settings)?; } } - let num_bytes = bytes.len(); - if num_bytes >= num_bytes_remaining_in_current_chunk { - num_bytes_remaining_in_current_chunk = chunk_size; - i += 1; - } else { - num_bytes_remaining_in_current_chunk -= num_bytes; + // Advance to the next chunk if the current one is filled. + // There could be a situation when a long line, which started in current chunk, + // would overlap the next chunk (or even several next chunks), + // and since we cannot break lines for this split strategy, we could end up with + // empty files in place(s) of skipped chunk(s) + let num_line_bytes = bytes.len() as u64; + num_bytes_written += num_line_bytes; + let mut skipped = -1; + while num_bytes_should_be_written <= num_bytes_written { + num_bytes_should_be_written += + chunk_size_base + (chunk_size_reminder > chunk_number) as u64; + chunk_number += 1; + skipped += 1; } - if let Some(chunk_number) = kth_chunk { - if i > chunk_number { + // If a chunk was skipped and `elide_empty_files` flag is set, + // roll chunk_number back to preserve sequential continuity + // of file names for files written to, + // except for Kth chunk of N mode + if settings.elide_empty_files && skipped > 0 && kth_chunk.is_none() { + chunk_number -= skipped as u64; + } + + if let Some(kth) = kth_chunk { + if chunk_number > kth { break; } } } - Ok(()) } @@ -1432,23 +1503,14 @@ where // In Kth chunk of N mode - we will write to stdout instead of to a file. let mut stdout_writer = std::io::stdout().lock(); // In N chunks mode - we will write to `num_chunks` files - let mut writers = vec![]; + let mut out_files: Vec = Vec::new(); // If in N chunks mode // Create one writer for each chunk. // This will create each of the underlying files // or stdin pipes to child shell/command processes if in `--filter` mode if kth_chunk.is_none() { - // This object is responsible for creating the filename for each chunk. - let mut filename_iterator = FilenameIterator::new(&settings.prefix, &settings.suffix) - .map_err(|e| io::Error::new(ErrorKind::Other, format!("{e}")))?; - for _ in 0..num_chunks { - let filename = filename_iterator - .next() - .ok_or_else(|| USimpleError::new(1, "output file suffixes exhausted"))?; - let writer = settings.instantiate_current_writer(filename.as_str())?; - writers.push(writer); - } + out_files = get_out_files(num_chunks, settings, false)?; } let num_chunks: usize = num_chunks.try_into().unwrap(); @@ -1470,9 +1532,7 @@ where } } None => { - let maybe_writer = writers.get_mut(i % num_chunks); - let writer = maybe_writer.unwrap(); - + let writer = out_files[i % num_chunks].get_writer(settings)?; let writer_stdin_open = custom_write_all(bytes, writer, settings)?; if !writer_stdin_open { closed_writers += 1; diff --git a/src/uu/split/src/strategy.rs b/src/uu/split/src/strategy.rs index e85abcee58b..7b934f72047 100644 --- a/src/uu/split/src/strategy.rs +++ b/src/uu/split/src/strategy.rs @@ -8,7 +8,10 @@ use crate::{OPT_BYTES, OPT_LINES, OPT_LINE_BYTES, OPT_NUMBER}; use clap::{parser::ValueSource, ArgMatches}; use std::fmt; -use uucore::parse_size::{parse_size_u64, parse_size_u64_max, ParseSizeError}; +use uucore::{ + display::Quotable, + parse_size::{parse_size_u64, parse_size_u64_max, ParseSizeError}, +}; /// Sub-strategy of the [`Strategy::Number`] /// Splitting a file into a specific number of chunks. @@ -208,10 +211,10 @@ impl fmt::Display for StrategyError { Self::Lines(e) => write!(f, "invalid number of lines: {e}"), Self::Bytes(e) => write!(f, "invalid number of bytes: {e}"), Self::NumberType(NumberTypeError::NumberOfChunks(s)) => { - write!(f, "invalid number of chunks: {s}") + write!(f, "invalid number of chunks: {}", s.quote()) } Self::NumberType(NumberTypeError::ChunkNumber(s)) => { - write!(f, "invalid chunk number: {s}") + write!(f, "invalid chunk number: {}", s.quote()) } Self::MultipleWays => write!(f, "cannot split in more than one way"), } diff --git a/tests/by-util/test_split.rs b/tests/by-util/test_split.rs index 0ae2af5cb92..2c9a56bddee 100644 --- a/tests/by-util/test_split.rs +++ b/tests/by-util/test_split.rs @@ -606,13 +606,13 @@ fn test_split_obs_lines_as_other_option_value() { .args(&["-n", "-200", "file"]) .fails() .code_is(1) - .stderr_contains("split: invalid number of chunks: -200\n"); + .stderr_contains("split: invalid number of chunks: '-200'\n"); scene .ucmd() .args(&["--number", "-e200", "file"]) .fails() .code_is(1) - .stderr_contains("split: invalid number of chunks: -e200\n"); + .stderr_contains("split: invalid number of chunks: '-e200'\n"); } /// Test for using more than one obsolete lines option (standalone) @@ -708,7 +708,7 @@ fn test_split_overflow_bytes_size() { fn test_split_stdin_num_chunks() { let (at, mut ucmd) = at_and_ucmd!(); ucmd.args(&["--number=1"]).pipe_in("").succeeds(); - assert_eq!(file_read(&at, "xaa"), ""); + assert_eq!(at.read("xaa"), ""); assert!(!at.plus("xab").exists()); } @@ -727,8 +727,8 @@ fn test_split_stdin_num_line_chunks() { ucmd.args(&["--number=l/2"]) .pipe_in("1\n2\n3\n4\n5\n") .succeeds(); - assert_eq!(file_read(&at, "xaa"), "1\n2\n3\n"); - assert_eq!(file_read(&at, "xab"), "4\n5\n"); + assert_eq!(at.read("xaa"), "1\n2\n3\n"); + assert_eq!(at.read("xab"), "4\n5\n"); assert!(!at.plus("xac").exists()); } @@ -741,12 +741,6 @@ fn test_split_stdin_num_kth_line_chunk() { .stdout_only("2\n"); } -fn file_read(at: &AtPath, filename: &str) -> String { - let mut s = String::new(); - at.open(filename).read_to_string(&mut s).unwrap(); - s -} - /// Test for the default suffix length behavior: dynamically increasing size. #[test] fn test_alphabetic_dynamic_suffix_length() { @@ -766,11 +760,11 @@ fn test_alphabetic_dynamic_suffix_length() { for i in b'a'..=b'y' { for j in b'a'..=b'z' { let filename = format!("x{}{}", i as char, j as char); - let contents = file_read(&at, &filename); + let contents = at.read(&filename); assert_eq!(contents, "a"); } } - assert_eq!(file_read(&at, "xzaaa"), "a"); + assert_eq!(at.read("xzaaa"), "a"); } /// Test for the default suffix length behavior: dynamically increasing size. @@ -790,10 +784,10 @@ fn test_numeric_dynamic_suffix_length() { .succeeds(); for i in 0..90 { let filename = format!("x{i:02}"); - let contents = file_read(&at, &filename); + let contents = at.read(&filename); assert_eq!(contents, "a"); } - assert_eq!(file_read(&at, "x9000"), "a"); + assert_eq!(at.read("x9000"), "a"); } #[test] @@ -812,10 +806,10 @@ fn test_hex_dynamic_suffix_length() { .succeeds(); for i in 0..240 { let filename = format!("x{i:02x}"); - let contents = file_read(&at, &filename); + let contents = at.read(&filename); assert_eq!(contents, "a"); } - assert_eq!(file_read(&at, "xf000"), "a"); + assert_eq!(at.read("xf000"), "a"); } /// Test for dynamic suffix length (auto-widening) disabled when suffix start number is specified @@ -833,7 +827,7 @@ fn test_dynamic_suffix_length_on_with_suffix_start_no_value() { let (at, mut ucmd) = at_and_ucmd!(); ucmd.args(&["-b", "1", "--numeric-suffixes", "ninetyonebytes.txt"]) .succeeds(); - assert_eq!(file_read(&at, "x9000"), "a"); + assert_eq!(at.read("x9000"), "a"); } /// Test for suffix auto-width with --number strategy and suffix start number @@ -845,8 +839,8 @@ fn test_suffix_auto_width_with_number() { let glob = Glob::new(&at, ".", r"x\d\d\d$"); assert_eq!(glob.count(), 100); assert_eq!(glob.collate(), at.read_bytes("fivelines.txt")); - assert_eq!(file_read(&at, "x001"), "1\n"); - assert_eq!(file_read(&at, "x100"), ""); + assert_eq!(at.read("x001"), "1\n"); + assert_eq!(at.read("x100"), ""); new_ucmd!() .args(&["--numeric-suffixes=100", "--number=r/100", "fivelines.txt"]) @@ -926,17 +920,12 @@ creating file 'xaf' #[test] fn test_number_n() { let (at, mut ucmd) = at_and_ucmd!(); - let file_read = |f| { - let mut s = String::new(); - at.open(f).read_to_string(&mut s).unwrap(); - s - }; ucmd.args(&["-n", "5", "asciilowercase.txt"]).succeeds(); - assert_eq!(file_read("xaa"), "abcdef"); - assert_eq!(file_read("xab"), "ghijkl"); - assert_eq!(file_read("xac"), "mnopq"); - assert_eq!(file_read("xad"), "rstuv"); - assert_eq!(file_read("xae"), "wxyz\n"); + assert_eq!(at.read("xaa"), "abcdef"); + assert_eq!(at.read("xab"), "ghijkl"); + assert_eq!(at.read("xac"), "mnopq"); + assert_eq!(at.read("xad"), "rstuv"); + assert_eq!(at.read("xae"), "wxyz\n"); #[cfg(unix)] new_ucmd!() .args(&["--number=100", "/dev/null"]) @@ -974,11 +963,11 @@ fn test_number_kth_of_n() { new_ucmd!() .args(&["--number=0/5", "asciilowercase.txt"]) .fails() - .stderr_contains("split: invalid chunk number: 0"); + .stderr_contains("split: invalid chunk number: '0'"); new_ucmd!() .args(&["--number=10/5", "asciilowercase.txt"]) .fails() - .stderr_contains("split: invalid chunk number: 10"); + .stderr_contains("split: invalid chunk number: '10'"); #[cfg(target_pointer_width = "64")] new_ucmd!() .args(&[ @@ -986,7 +975,7 @@ fn test_number_kth_of_n() { "asciilowercase.txt", ]) .fails() - .stderr_contains("split: invalid number of chunks: 18446744073709551616"); + .stderr_contains("split: invalid number of chunks: '18446744073709551616'"); } #[test] @@ -1020,32 +1009,27 @@ fn test_number_kth_of_n_round_robin() { "fivelines.txt", ]) .fails() - .stderr_contains("split: invalid number of chunks: 18446744073709551616"); + .stderr_contains("split: invalid number of chunks: '18446744073709551616'"); new_ucmd!() .args(&["--number", "r/0/3", "fivelines.txt"]) .fails() - .stderr_contains("split: invalid chunk number: 0"); + .stderr_contains("split: invalid chunk number: '0'"); new_ucmd!() .args(&["--number", "r/10/3", "fivelines.txt"]) .fails() - .stderr_contains("split: invalid chunk number: 10"); + .stderr_contains("split: invalid chunk number: '10'"); } #[test] fn test_split_number_with_io_blksize() { let (at, mut ucmd) = at_and_ucmd!(); - let file_read = |f| { - let mut s = String::new(); - at.open(f).read_to_string(&mut s).unwrap(); - s - }; ucmd.args(&["-n", "5", "asciilowercase.txt", "---io-blksize", "1024"]) .succeeds(); - assert_eq!(file_read("xaa"), "abcdef"); - assert_eq!(file_read("xab"), "ghijkl"); - assert_eq!(file_read("xac"), "mnopq"); - assert_eq!(file_read("xad"), "rstuv"); - assert_eq!(file_read("xae"), "wxyz\n"); + assert_eq!(at.read("xaa"), "abcdef"); + assert_eq!(at.read("xab"), "ghijkl"); + assert_eq!(at.read("xac"), "mnopq"); + assert_eq!(at.read("xad"), "rstuv"); + assert_eq!(at.read("xae"), "wxyz\n"); } #[test] @@ -1153,7 +1137,7 @@ fn test_allow_empty_files() { } #[test] -fn test_elide_empty_files() { +fn test_elide_empty_files_n_chunks() { let (at, mut ucmd) = at_and_ucmd!(); ucmd.args(&["-e", "-n", "4", "threebytes.txt"]) .succeeds() @@ -1167,7 +1151,7 @@ fn test_elide_empty_files() { #[test] #[cfg(unix)] -fn test_elide_dev_null() { +fn test_elide_dev_null_n_chunks() { let (at, mut ucmd) = at_and_ucmd!(); ucmd.args(&["-e", "-n", "3", "/dev/null"]) .succeeds() @@ -1191,24 +1175,58 @@ fn test_dev_zero() { } #[test] -fn test_lines() { +fn test_elide_empty_files_l_chunks() { let (at, mut ucmd) = at_and_ucmd!(); + ucmd.args(&["-e", "-n", "l/7", "fivelines.txt"]) + .succeeds() + .no_stdout() + .no_stderr(); + assert_eq!(at.read("xaa"), "1\n"); + assert_eq!(at.read("xab"), "2\n"); + assert_eq!(at.read("xac"), "3\n"); + assert_eq!(at.read("xad"), "4\n"); + assert_eq!(at.read("xae"), "5\n"); + assert!(!at.plus("xaf").exists()); + assert!(!at.plus("xag").exists()); +} - let file_read = |f| { - let mut s = String::new(); - at.open(f).read_to_string(&mut s).unwrap(); - s - }; +#[test] +#[cfg(unix)] +fn test_elide_dev_null_l_chunks() { + let (at, mut ucmd) = at_and_ucmd!(); + ucmd.args(&["-e", "-n", "l/3", "/dev/null"]) + .succeeds() + .no_stdout() + .no_stderr(); + assert!(!at.plus("xaa").exists()); + assert!(!at.plus("xab").exists()); + assert!(!at.plus("xac").exists()); +} +#[test] +#[cfg(unix)] +fn test_number_by_bytes_dev_zero() { + let (at, mut ucmd) = at_and_ucmd!(); + ucmd.args(&["-n", "3", "/dev/zero"]) + .fails() + .stderr_only("split: /dev/zero: cannot determine file size\n"); + assert!(!at.plus("xaa").exists()); + assert!(!at.plus("xab").exists()); + assert!(!at.plus("xac").exists()); +} + +#[test] +fn test_number_by_lines() { + let (at, mut ucmd) = at_and_ucmd!(); // Split into two files without splitting up lines. ucmd.args(&["-n", "l/2", "fivelines.txt"]).succeeds(); - assert_eq!(file_read("xaa"), "1\n2\n3\n"); - assert_eq!(file_read("xab"), "4\n5\n"); + assert_eq!(at.read("xaa"), "1\n2\n3\n"); + assert_eq!(at.read("xab"), "4\n5\n"); } #[test] -fn test_lines_kth() { +fn test_number_by_lines_kth() { new_ucmd!() .args(&["-n", "l/3/10", "onehundredlines.txt"]) .succeeds() @@ -1217,13 +1235,27 @@ fn test_lines_kth() { #[test] #[cfg(unix)] -fn test_lines_kth_dev_null() { +fn test_number_by_lines_kth_dev_null() { new_ucmd!() .args(&["-n", "l/3/10", "/dev/null"]) .succeeds() .stdout_only(""); } +#[test] +fn test_number_by_lines_kth_no_end_sep() { + new_ucmd!() + .args(&["-n", "l/3/10"]) + .pipe_in("1\n2222\n3\n4") + .succeeds() + .stdout_only("2222\n"); + new_ucmd!() + .args(&["-e", "-n", "l/8/10"]) + .pipe_in("1\n2222\n3\n4") + .succeeds() + .stdout_only("3\n"); +} + #[test] fn test_line_bytes() { let (at, mut ucmd) = at_and_ucmd!(); @@ -1588,17 +1620,10 @@ fn test_effective_suffix_hex_last() { #[test] fn test_round_robin() { let (at, mut ucmd) = at_and_ucmd!(); - - let file_read = |f| { - let mut s = String::new(); - at.open(f).read_to_string(&mut s).unwrap(); - s - }; - ucmd.args(&["-n", "r/2", "fivelines.txt"]).succeeds(); - assert_eq!(file_read("xaa"), "1\n3\n5\n"); - assert_eq!(file_read("xab"), "2\n4\n"); + assert_eq!(at.read("xaa"), "1\n3\n5\n"); + assert_eq!(at.read("xab"), "2\n4\n"); } #[test] @@ -1631,7 +1656,7 @@ fn test_split_invalid_input() { .args(&["-n", "0", "file"]) .fails() .no_stdout() - .stderr_contains("split: invalid number of chunks: 0"); + .stderr_contains("split: invalid number of chunks: '0'"); } /// Test if there are invalid (non UTF-8) in the arguments - unix @@ -1690,9 +1715,9 @@ fn test_split_separator_nl_lines() { .pipe_in("1\n2\n3\n4\n5\n") .succeeds(); - assert_eq!(file_read(&at, "xaa"), "1\n2\n"); - assert_eq!(file_read(&at, "xab"), "3\n4\n"); - assert_eq!(file_read(&at, "xac"), "5\n"); + assert_eq!(at.read("xaa"), "1\n2\n"); + assert_eq!(at.read("xab"), "3\n4\n"); + assert_eq!(at.read("xac"), "5\n"); assert!(!at.plus("xad").exists()); } @@ -1703,9 +1728,9 @@ fn test_split_separator_nl_line_bytes() { .pipe_in("1\n2\n3\n4\n5\n") .succeeds(); - assert_eq!(file_read(&at, "xaa"), "1\n2\n"); - assert_eq!(file_read(&at, "xab"), "3\n4\n"); - assert_eq!(file_read(&at, "xac"), "5\n"); + assert_eq!(at.read("xaa"), "1\n2\n"); + assert_eq!(at.read("xab"), "3\n4\n"); + assert_eq!(at.read("xac"), "5\n"); assert!(!at.plus("xad").exists()); } @@ -1715,9 +1740,9 @@ fn test_split_separator_nl_number_l() { ucmd.args(&["--number=l/3", "--separator=\n", "fivelines.txt"]) .succeeds(); - assert_eq!(file_read(&at, "xaa"), "1\n2\n"); - assert_eq!(file_read(&at, "xab"), "3\n4\n"); - assert_eq!(file_read(&at, "xac"), "5\n"); + assert_eq!(at.read("xaa"), "1\n2\n"); + assert_eq!(at.read("xab"), "3\n4\n"); + assert_eq!(at.read("xac"), "5\n"); assert!(!at.plus("xad").exists()); } @@ -1727,9 +1752,9 @@ fn test_split_separator_nl_number_r() { ucmd.args(&["--number=r/3", "--separator", "\n", "fivelines.txt"]) .succeeds(); - assert_eq!(file_read(&at, "xaa"), "1\n4\n"); - assert_eq!(file_read(&at, "xab"), "2\n5\n"); - assert_eq!(file_read(&at, "xac"), "3\n"); + assert_eq!(at.read("xaa"), "1\n4\n"); + assert_eq!(at.read("xab"), "2\n5\n"); + assert_eq!(at.read("xac"), "3\n"); assert!(!at.plus("xad").exists()); } @@ -1739,9 +1764,9 @@ fn test_split_separator_nul_lines() { ucmd.args(&["--lines=2", "-t", "\\0", "separator_nul.txt"]) .succeeds(); - assert_eq!(file_read(&at, "xaa"), "1\x002\0"); - assert_eq!(file_read(&at, "xab"), "3\x004\0"); - assert_eq!(file_read(&at, "xac"), "5\0"); + assert_eq!(at.read("xaa"), "1\x002\0"); + assert_eq!(at.read("xab"), "3\x004\0"); + assert_eq!(at.read("xac"), "5\0"); assert!(!at.plus("xad").exists()); } @@ -1751,9 +1776,9 @@ fn test_split_separator_nul_line_bytes() { ucmd.args(&["--line-bytes=4", "-t", "\\0", "separator_nul.txt"]) .succeeds(); - assert_eq!(file_read(&at, "xaa"), "1\x002\0"); - assert_eq!(file_read(&at, "xab"), "3\x004\0"); - assert_eq!(file_read(&at, "xac"), "5\0"); + assert_eq!(at.read("xaa"), "1\x002\0"); + assert_eq!(at.read("xab"), "3\x004\0"); + assert_eq!(at.read("xac"), "5\0"); assert!(!at.plus("xad").exists()); } @@ -1763,9 +1788,9 @@ fn test_split_separator_nul_number_l() { ucmd.args(&["--number=l/3", "--separator=\\0", "separator_nul.txt"]) .succeeds(); - assert_eq!(file_read(&at, "xaa"), "1\x002\0"); - assert_eq!(file_read(&at, "xab"), "3\x004\0"); - assert_eq!(file_read(&at, "xac"), "5\0"); + assert_eq!(at.read("xaa"), "1\x002\0"); + assert_eq!(at.read("xab"), "3\x004\0"); + assert_eq!(at.read("xac"), "5\0"); assert!(!at.plus("xad").exists()); } @@ -1775,9 +1800,9 @@ fn test_split_separator_nul_number_r() { ucmd.args(&["--number=r/3", "--separator=\\0", "separator_nul.txt"]) .succeeds(); - assert_eq!(file_read(&at, "xaa"), "1\x004\0"); - assert_eq!(file_read(&at, "xab"), "2\x005\0"); - assert_eq!(file_read(&at, "xac"), "3\0"); + assert_eq!(at.read("xaa"), "1\x004\0"); + assert_eq!(at.read("xab"), "2\x005\0"); + assert_eq!(at.read("xac"), "3\0"); assert!(!at.plus("xad").exists()); } @@ -1787,9 +1812,9 @@ fn test_split_separator_semicolon_lines() { ucmd.args(&["--lines=2", "-t", ";", "separator_semicolon.txt"]) .succeeds(); - assert_eq!(file_read(&at, "xaa"), "1;2;"); - assert_eq!(file_read(&at, "xab"), "3;4;"); - assert_eq!(file_read(&at, "xac"), "5;"); + assert_eq!(at.read("xaa"), "1;2;"); + assert_eq!(at.read("xab"), "3;4;"); + assert_eq!(at.read("xac"), "5;"); assert!(!at.plus("xad").exists()); } @@ -1799,9 +1824,9 @@ fn test_split_separator_semicolon_line_bytes() { ucmd.args(&["--line-bytes=4", "-t", ";", "separator_semicolon.txt"]) .succeeds(); - assert_eq!(file_read(&at, "xaa"), "1;2;"); - assert_eq!(file_read(&at, "xab"), "3;4;"); - assert_eq!(file_read(&at, "xac"), "5;"); + assert_eq!(at.read("xaa"), "1;2;"); + assert_eq!(at.read("xab"), "3;4;"); + assert_eq!(at.read("xac"), "5;"); assert!(!at.plus("xad").exists()); } @@ -1811,9 +1836,9 @@ fn test_split_separator_semicolon_number_l() { ucmd.args(&["--number=l/3", "--separator=;", "separator_semicolon.txt"]) .succeeds(); - assert_eq!(file_read(&at, "xaa"), "1;2;"); - assert_eq!(file_read(&at, "xab"), "3;4;"); - assert_eq!(file_read(&at, "xac"), "5;"); + assert_eq!(at.read("xaa"), "1;2;"); + assert_eq!(at.read("xab"), "3;4;"); + assert_eq!(at.read("xac"), "5;"); assert!(!at.plus("xad").exists()); } @@ -1823,9 +1848,9 @@ fn test_split_separator_semicolon_number_r() { ucmd.args(&["--number=r/3", "--separator=;", "separator_semicolon.txt"]) .succeeds(); - assert_eq!(file_read(&at, "xaa"), "1;4;"); - assert_eq!(file_read(&at, "xab"), "2;5;"); - assert_eq!(file_read(&at, "xac"), "3;"); + assert_eq!(at.read("xaa"), "1;4;"); + assert_eq!(at.read("xab"), "2;5;"); + assert_eq!(at.read("xac"), "3;"); assert!(!at.plus("xad").exists()); } From 84b5e6f0a1275ffd6816d4478284ed2ef27e5f1e Mon Sep 17 00:00:00 2001 From: Clint Teece Date: Sat, 25 Nov 2023 09:13:12 -0500 Subject: [PATCH 0396/2851] du: start printing output immediately (#5552) * du: very rough draft of continuously printing output * du: clean up printing logic, still needs some polishing * du: gracefully handle case where `du` returns no `Stat`s * du: print output using separate thread * du: clean up print thread implementation * du: send ownership of `Stat`s to printing thread as soon as `du` is done with them * du: add basic error handling for communication between threads, use `StatPrinter` to handle printing thread logic * du: move printing grand total into `StatPrinter`, and move initialization of printing-related variables into `StatPrinter::new` * du: clean up calculation of `convert_size` function, and separate printing a single stat our into its own method in `StatPrinter` * du: have printing thread handle printing IO-related errors, to ensure error messages and regular output message are written one at a time * du: add comment explaining print thread, remove outdated comments and clippy allows * du: restore clippy allows for cognitive complexity --------- Co-authored-by: clint --- src/uu/du/src/du.rs | 317 +++++++++++++++++++++++++++----------------- 1 file changed, 193 insertions(+), 124 deletions(-) diff --git a/src/uu/du/src/du.rs b/src/uu/du/src/du.rs index 148b197df33..dc03a64f218 100644 --- a/src/uu/du/src/du.rs +++ b/src/uu/du/src/du.rs @@ -16,8 +16,6 @@ use std::fs::File; use std::fs::Metadata; use std::io::BufRead; use std::io::BufReader; -use std::io::Result; -use std::iter; #[cfg(not(windows))] use std::os::unix::fs::MetadataExt; #[cfg(windows)] @@ -27,15 +25,17 @@ use std::os::windows::io::AsRawHandle; use std::path::Path; use std::path::PathBuf; use std::str::FromStr; +use std::sync::mpsc; +use std::thread; use std::time::{Duration, UNIX_EPOCH}; use std::{error::Error, fmt::Display}; use uucore::display::{print_verbatim, Quotable}; use uucore::error::FromIo; -use uucore::error::{set_exit_code, UError, UResult, USimpleError}; +use uucore::error::{UError, UResult, USimpleError}; use uucore::line_ending::LineEnding; use uucore::parse_glob; use uucore::parse_size::{parse_size_u64, ParseSizeError}; -use uucore::{format_usage, help_about, help_section, help_usage, show, show_error, show_warning}; +use uucore::{format_usage, help_about, help_section, help_usage, show, show_warning}; #[cfg(windows)] use windows_sys::Win32::Foundation::HANDLE; #[cfg(windows)] @@ -81,6 +81,7 @@ const USAGE: &str = help_usage!("du.md"); // TODO: Support Z & Y (currently limited by size of u64) const UNITS: [(char, u32); 6] = [('E', 6), ('P', 5), ('T', 4), ('G', 3), ('M', 2), ('K', 1)]; +#[derive(Clone)] struct Options { all: bool, max_depth: Option, @@ -93,7 +94,7 @@ struct Options { verbose: bool, } -#[derive(PartialEq)] +#[derive(PartialEq, Clone)] enum Deref { All, Args(Vec), @@ -119,7 +120,7 @@ struct Stat { } impl Stat { - fn new(path: &Path, options: &Options) -> Result { + fn new(path: &Path, options: &Options) -> std::io::Result { // Determine whether to dereference (follow) the symbolic link let should_dereference = match &options.dereference { Deref::All => true, @@ -290,7 +291,6 @@ fn choose_size(matches: &ArgMatches, stat: &Stat) -> u64 { } // this takes `my_stat` to avoid having to stat files multiple times. -// XXX: this should use the impl Trait return type when it is stabilized #[allow(clippy::cognitive_complexity)] fn du( mut my_stat: Stat, @@ -298,18 +298,16 @@ fn du( depth: usize, seen_inodes: &mut HashSet, exclude: &[Pattern], -) -> Box> { - let mut stats = vec![]; - let mut futures = vec![]; - + print_tx: &mpsc::Sender>, +) -> Result>>> { if my_stat.is_dir { let read = match fs::read_dir(&my_stat.path) { Ok(read) => read, Err(e) => { - show!( - e.map_err_context(|| format!("cannot read directory {}", my_stat.path.quote())) - ); - return Box::new(iter::once(my_stat)); + print_tx.send(Err(e.map_err_context(|| { + format!("cannot read directory {}", my_stat.path.quote()) + })))?; + return Ok(my_stat); } }; @@ -354,44 +352,48 @@ fn du( } } } - futures.push(du( + + let this_stat = du( this_stat, options, depth + 1, seen_inodes, exclude, - )); + print_tx, + )?; + + if !options.separate_dirs { + my_stat.size += this_stat.size; + my_stat.blocks += this_stat.blocks; + my_stat.inodes += this_stat.inodes; + } + print_tx.send(Ok(StatPrintInfo { + stat: this_stat, + depth: depth + 1, + }))?; } else { my_stat.size += this_stat.size; my_stat.blocks += this_stat.blocks; my_stat.inodes += 1; if options.all { - stats.push(this_stat); + print_tx.send(Ok(StatPrintInfo { + stat: this_stat, + depth: depth + 1, + }))?; } } } - Err(e) => show!( - e.map_err_context(|| format!("cannot access {}", entry.path().quote())) - ), + Err(e) => print_tx.send(Err(e.map_err_context(|| { + format!("cannot access {}", entry.path().quote()) + })))?, } } - Err(error) => show_error!("{}", error), + Err(error) => print_tx.send(Err(error.into()))?, } } } - stats.extend(futures.into_iter().flatten().filter(|stat| { - if !options.separate_dirs && stat.path.parent().unwrap() == my_stat.path { - my_stat.size += stat.size; - my_stat.blocks += stat.blocks; - my_stat.inodes += stat.inodes; - } - options - .max_depth - .map_or(true, |max_depth| depth < max_depth) - })); - stats.push(my_stat); - Box::new(stats.into_iter()) + Ok(my_stat) } fn convert_size_human(size: u64, multiplier: u64, _block_size: u64) -> String { @@ -426,7 +428,7 @@ fn convert_size_other(size: u64, _multiplier: u64, block_size: u64) -> String { format!("{}", ((size as f64) / (block_size as f64)).ceil()) } -fn get_convert_size_fn(matches: &ArgMatches) -> Box String> { +fn get_convert_size_fn(matches: &ArgMatches) -> Box String + Send> { if matches.get_flag(options::HUMAN_READABLE) || matches.get_flag(options::SI) { Box::new(convert_size_human) } else if matches.get_flag(options::BYTES) { @@ -532,6 +534,137 @@ fn build_exclude_patterns(matches: &ArgMatches) -> UResult> { Ok(exclude_patterns) } +struct StatPrintInfo { + stat: Stat, + depth: usize, +} + +struct StatPrinter { + matches: ArgMatches, + threshold: Option, + summarize: bool, + time_format_str: String, + line_ending: LineEnding, + options: Options, + convert_size: Box String + Send>, +} + +impl StatPrinter { + fn new(matches: ArgMatches, options: Options, summarize: bool) -> UResult { + let block_size = read_block_size( + matches + .get_one::(options::BLOCK_SIZE) + .map(|s| s.as_str()), + )?; + + let multiplier: u64 = if matches.get_flag(options::SI) { + 1000 + } else { + 1024 + }; + + let convert_size_fn = get_convert_size_fn(&matches); + + let convert_size: Box String + Send> = if options.inodes { + Box::new(|size: u64| size.to_string()) + } else { + Box::new(move |size: u64| convert_size_fn(size, multiplier, block_size)) + }; + + let threshold = match matches.get_one::(options::THRESHOLD) { + Some(s) => match Threshold::from_str(s) { + Ok(t) => Some(t), + Err(e) => { + return Err(USimpleError::new( + 1, + format_error_message(&e, s, options::THRESHOLD), + )) + } + }, + None => None, + }; + + let time_format_str = + parse_time_style(matches.get_one::("time-style").map(|s| s.as_str()))? + .to_string(); + + let line_ending = LineEnding::from_zero_flag(matches.get_flag(options::NULL)); + + Ok(Self { + matches, + threshold, + summarize, + time_format_str, + line_ending, + options, + convert_size, + }) + } + + fn print_stats(&self, rx: &mpsc::Receiver>) -> UResult<()> { + let mut grand_total = 0; + loop { + let received = rx.recv(); + + match received { + Ok(message) => match message { + Ok(stat_info) => { + let size = choose_size(&self.matches, &stat_info.stat); + + if stat_info.depth == 0 { + grand_total += size; + } + + if !self + .threshold + .map_or(false, |threshold| threshold.should_exclude(size)) + && self + .options + .max_depth + .map_or(true, |max_depth| stat_info.depth <= max_depth) + && (!self.summarize || stat_info.depth == 0) + { + self.print_stat(&stat_info.stat, size)?; + } + } + Err(e) => show!(e), + }, + Err(_) => break, + } + } + + if self.options.total { + print!("{}\ttotal", (self.convert_size)(grand_total)); + print!("{}", self.line_ending); + } + + Ok(()) + } + + fn print_stat(&self, stat: &Stat, size: u64) -> UResult<()> { + if self.matches.contains_id(options::TIME) { + let tm = { + let secs = self + .matches + .get_one::(options::TIME) + .map(|s| get_time_secs(s, stat)) + .transpose()? + .unwrap_or(stat.modified); + DateTime::::from(UNIX_EPOCH + Duration::from_secs(secs)) + }; + let time_str = tm.format(&self.time_format_str).to_string(); + print!("{}\t{}\t", (self.convert_size)(size), time_str); + } else { + print!("{}\t", (self.convert_size)(size)); + } + + print_verbatim(&stat.path).unwrap(); + print!("{}", self.line_ending); + + Ok(()) + } +} + #[uucore::main] #[allow(clippy::cognitive_complexity)] pub fn uumain(args: impl uucore::Args) -> UResult<()> { @@ -582,49 +715,13 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { show_warning!("options --apparent-size and -b are ineffective with --inodes"); } - let block_size = read_block_size( - matches - .get_one::(options::BLOCK_SIZE) - .map(|s| s.as_str()), - )?; - - let threshold = match matches.get_one::(options::THRESHOLD) { - Some(s) => match Threshold::from_str(s) { - Ok(t) => Some(t), - Err(e) => { - return Err(USimpleError::new( - 1, - format_error_message(&e, s, options::THRESHOLD), - )) - } - }, - None => None, - }; - - let multiplier: u64 = if matches.get_flag(options::SI) { - 1000 - } else { - 1024 - }; - - let convert_size_fn = get_convert_size_fn(&matches); - - let convert_size = |size: u64| { - if options.inodes { - size.to_string() - } else { - convert_size_fn(size, multiplier, block_size) - } - }; - - let time_format_str = - parse_time_style(matches.get_one::("time-style").map(|s| s.as_str()))?; - - let line_ending = LineEnding::from_zero_flag(matches.get_flag(options::NULL)); + // Use separate thread to print output, so we can print finished results while computation is still running + let stat_printer = StatPrinter::new(matches.clone(), options.clone(), summarize)?; + let (print_tx, rx) = mpsc::channel::>(); + let printing_thread = thread::spawn(move || stat_printer.print_stats(&rx)); let excludes = build_exclude_patterns(&matches)?; - let mut grand_total = 0; 'loop_file: for path in files { // Skip if we don't want to ignore anything if !&excludes.is_empty() { @@ -647,63 +744,35 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { if let Some(inode) = stat.inode { seen_inodes.insert(inode); } - let iter = du(stat, &options, 0, &mut seen_inodes, &excludes); - - // Sum up all the returned `Stat`s and display results - let (_, len) = iter.size_hint(); - let len = len.unwrap(); - for (index, stat) in iter.enumerate() { - let size = choose_size(&matches, &stat); + let stat = du(stat, &options, 0, &mut seen_inodes, &excludes, &print_tx) + .map_err(|e| USimpleError::new(1, e.to_string()))?; - if threshold.map_or(false, |threshold| threshold.should_exclude(size)) { - continue; - } - - if matches.contains_id(options::TIME) { - let tm = { - let secs = matches - .get_one::(options::TIME) - .map(|s| get_time_secs(s, &stat)) - .transpose()? - .unwrap_or(stat.modified); - DateTime::::from(UNIX_EPOCH + Duration::from_secs(secs)) - }; - if !summarize || index == len - 1 { - let time_str = tm.format(time_format_str).to_string(); - print!("{}\t{}\t", convert_size(size), time_str); - print_verbatim(stat.path).unwrap(); - print!("{line_ending}"); - } - } else if !summarize || index == len - 1 { - print!("{}\t", convert_size(size)); - print_verbatim(stat.path).unwrap(); - print!("{line_ending}"); - } - if options.total && index == (len - 1) { - // The last element will be the total size of the the path under - // path_string. We add it to the grand total. - grand_total += size; - } - } + print_tx + .send(Ok(StatPrintInfo { stat, depth: 0 })) + .map_err(|e| USimpleError::new(1, e.to_string()))?; } else { - show_error!( - "{}: {}", - path.to_string_lossy().maybe_quote(), - "No such file or directory" - ); - set_exit_code(1); + print_tx + .send(Err(USimpleError::new( + 1, + format!( + "{}: No such file or directory", + path.to_string_lossy().maybe_quote() + ), + ))) + .map_err(|e| USimpleError::new(1, e.to_string()))?; } } - if options.total { - print!("{}\ttotal", convert_size(grand_total)); - print!("{line_ending}"); - } + drop(print_tx); + + printing_thread + .join() + .map_err(|_| USimpleError::new(1, "Printing thread panicked."))??; Ok(()) } -fn get_time_secs(s: &str, stat: &Stat) -> std::result::Result { +fn get_time_secs(s: &str, stat: &Stat) -> Result { let secs = match s { "ctime" | "status" => stat.modified, "access" | "atime" | "use" => stat.accessed, @@ -966,7 +1035,7 @@ enum Threshold { impl FromStr for Threshold { type Err = ParseSizeError; - fn from_str(s: &str) -> std::result::Result { + fn from_str(s: &str) -> Result { let offset = usize::from(s.starts_with(&['-', '+'][..])); let size = parse_size_u64(&s[offset..])?; From dc92a434ef0f64a833d8b657268068eb3a25ab22 Mon Sep 17 00:00:00 2001 From: zhitkoff Date: Fri, 24 Nov 2023 17:25:16 -0500 Subject: [PATCH 0397/2851] split: handling system limit on open files --- src/uu/split/src/platform/unix.rs | 43 ++++-- src/uu/split/src/platform/windows.rs | 23 ++- src/uu/split/src/split.rs | 204 +++++++++++++++++++-------- 3 files changed, 191 insertions(+), 79 deletions(-) diff --git a/src/uu/split/src/platform/unix.rs b/src/uu/split/src/platform/unix.rs index c2bf7216b57..1fd990e0a91 100644 --- a/src/uu/split/src/platform/unix.rs +++ b/src/uu/split/src/platform/unix.rs @@ -117,22 +117,37 @@ impl Drop for FilterWriter { pub fn instantiate_current_writer( filter: &Option, filename: &str, + is_new: bool, ) -> Result>> { match filter { - None => Ok(BufWriter::new(Box::new( - // write to the next file - std::fs::OpenOptions::new() - .write(true) - .create(true) - .truncate(true) - .open(std::path::Path::new(&filename)) - .map_err(|_| { - Error::new( - ErrorKind::Other, - format!("unable to open '{filename}'; aborting"), - ) - })?, - ) as Box)), + None => { + let file = if is_new { + // create new file + std::fs::OpenOptions::new() + .write(true) + .create(true) + .truncate(true) + .open(std::path::Path::new(&filename)) + .map_err(|_| { + Error::new( + ErrorKind::Other, + format!("unable to open '{filename}'; aborting"), + ) + })? + } else { + // re-open file that we previously created to append to it + std::fs::OpenOptions::new() + .append(true) + .open(std::path::Path::new(&filename)) + .map_err(|_| { + Error::new( + ErrorKind::Other, + format!("unable to re-open '{filename}'; aborting"), + ) + })? + }; + Ok(BufWriter::new(Box::new(file) as Box)) + } Some(ref filter_command) => Ok(BufWriter::new(Box::new( // spawn a shell command and write to it FilterWriter::new(filter_command, filename)?, diff --git a/src/uu/split/src/platform/windows.rs b/src/uu/split/src/platform/windows.rs index 8b90789896f..a531d6abc1f 100644 --- a/src/uu/split/src/platform/windows.rs +++ b/src/uu/split/src/platform/windows.rs @@ -14,9 +14,10 @@ use uucore::fs; pub fn instantiate_current_writer( _filter: &Option, filename: &str, + is_new: bool, ) -> Result>> { - Ok(BufWriter::new(Box::new( - // write to the next file + let file = if is_new { + // create new file std::fs::OpenOptions::new() .write(true) .create(true) @@ -25,10 +26,22 @@ pub fn instantiate_current_writer( .map_err(|_| { Error::new( ErrorKind::Other, - format!("'{filename}' would overwrite input; aborting"), + format!("unable to open '{filename}'; aborting"), ) - })?, - ) as Box)) + })? + } else { + // re-open file that we previously created to append to it + std::fs::OpenOptions::new() + .append(true) + .open(std::path::Path::new(&filename)) + .map_err(|_| { + Error::new( + ErrorKind::Other, + format!("unable to re-open '{filename}'; aborting"), + ) + })? + }; + Ok(BufWriter::new(Box::new(file) as Box)) } pub fn paths_refer_to_same_file(p1: &str, p2: &str) -> bool { diff --git a/src/uu/split/src/split.rs b/src/uu/split/src/split.rs index 4e2af0be4d8..e5f9032c947 100644 --- a/src/uu/split/src/split.rs +++ b/src/uu/split/src/split.rs @@ -3,7 +3,7 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// spell-checker:ignore nbbbb ncccc hexdigit +// spell-checker:ignore nbbbb ncccc hexdigit getmaxstdio mod filenames; mod number; @@ -563,7 +563,11 @@ impl Settings { Ok(result) } - fn instantiate_current_writer(&self, filename: &str) -> io::Result>> { + fn instantiate_current_writer( + &self, + filename: &str, + is_new: bool, + ) -> io::Result>> { if platform::paths_refer_to_same_file(&self.input, filename) { return Err(io::Error::new( ErrorKind::Other, @@ -571,7 +575,7 @@ impl Settings { )); } - platform::instantiate_current_writer(&self.filter, filename) + platform::instantiate_current_writer(&self.filter, filename, is_new) } } @@ -748,7 +752,7 @@ impl<'a> ByteChunkWriter<'a> { if settings.verbose { println!("creating file {}", filename.quote()); } - let inner = settings.instantiate_current_writer(&filename)?; + let inner = settings.instantiate_current_writer(&filename, true)?; Ok(ByteChunkWriter { settings, chunk_size, @@ -786,7 +790,7 @@ impl<'a> Write for ByteChunkWriter<'a> { if self.settings.verbose { println!("creating file {}", filename.quote()); } - self.inner = self.settings.instantiate_current_writer(&filename)?; + self.inner = self.settings.instantiate_current_writer(&filename, true)?; } // If the capacity of this chunk is greater than the number of @@ -872,7 +876,7 @@ impl<'a> LineChunkWriter<'a> { if settings.verbose { println!("creating file {}", filename.quote()); } - let inner = settings.instantiate_current_writer(&filename)?; + let inner = settings.instantiate_current_writer(&filename, true)?; Ok(LineChunkWriter { settings, chunk_size, @@ -907,7 +911,7 @@ impl<'a> Write for LineChunkWriter<'a> { if self.settings.verbose { println!("creating file {}", filename.quote()); } - self.inner = self.settings.instantiate_current_writer(&filename)?; + self.inner = self.settings.instantiate_current_writer(&filename, true)?; self.num_lines_remaining_in_current_chunk = self.chunk_size; } @@ -979,7 +983,7 @@ impl<'a> LineBytesChunkWriter<'a> { if settings.verbose { println!("creating file {}", filename.quote()); } - let inner = settings.instantiate_current_writer(&filename)?; + let inner = settings.instantiate_current_writer(&filename, true)?; Ok(LineBytesChunkWriter { settings, chunk_size, @@ -1045,7 +1049,7 @@ impl<'a> Write for LineBytesChunkWriter<'a> { if self.settings.verbose { println!("creating file {}", filename.quote()); } - self.inner = self.settings.instantiate_current_writer(&filename)?; + self.inner = self.settings.instantiate_current_writer(&filename, true)?; self.num_bytes_remaining_in_current_chunk = self.chunk_size.try_into().unwrap(); } @@ -1134,55 +1138,135 @@ impl<'a> Write for LineBytesChunkWriter<'a> { struct OutFile { filename: String, maybe_writer: Option>>, + is_new: bool, } -impl OutFile { - /// Get the writer for the output file - /// Instantiate the writer if it has not been instantiated upfront - fn get_writer(&mut self, settings: &Settings) -> UResult<&mut BufWriter>> { - if self.maybe_writer.is_some() { - Ok(self.maybe_writer.as_mut().unwrap()) - } else { - // Writer was not instantiated upfront - // Instantiate it and record for future use - self.maybe_writer = Some(settings.instantiate_current_writer(self.filename.as_str())?); - Ok(self.maybe_writer.as_mut().unwrap()) +// impl OutFile { +// /// Get the writer for the output file. +// /// Instantiate the writer if it has not been instantiated upfront +// /// or temporarily closed to free up system resources +// fn get_writer(&mut self, settings: &Settings) -> UResult<&mut BufWriter>> { +// if self.maybe_writer.is_some() { +// Ok(self.maybe_writer.as_mut().unwrap()) +// } else { +// // Writer was not instantiated upfront or was temporarily closed due to system resources constraints. +// // Instantiate it and record for future use. +// self.maybe_writer = +// Some(settings.instantiate_current_writer(self.filename.as_str(), self.is_new)?); +// Ok(self.maybe_writer.as_mut().unwrap()) +// } +// } +// } + +/// A set of output files +/// Used in [`n_chunks_by_byte`], [`n_chunks_by_line`] +/// and [`n_chunks_by_line_round_robin`] functions. +type OutFiles = Vec; +trait ManageOutFiles { + /// Initialize a new set of output files + /// Each OutFile is generated with filename, while the writer for it could be + /// optional, to be instantiated later by the calling function as needed. + /// Optional writers could happen in the following situations: + /// * in [`n_chunks_by_line`] if `elide_empty_files` parameter is set to `true` + /// * if the number of files is greater than system limit for open files + fn init(num_files: u64, settings: &Settings, is_writer_optional: bool) -> UResult + where + Self: Sized; + /// Get the writer for the output file by index. + /// If system limit of open files has been reached + /// it will try to close one of previously instantiated writers + /// to free up resources and re-try instantiating current writer, + /// except for `--filter` mode. + /// The writers that get closed to free up resources for the current writer + /// are flagged as `is_new=false`, so they can be re-opened for appending + /// instead of created anew if we need to keep writing into them later, + /// i.e. in case of round robin distribution as in [`n_chunks_by_line_round_robin`] + fn get_writer( + &mut self, + idx: usize, + settings: &Settings, + ) -> UResult<&mut BufWriter>>; +} + +impl ManageOutFiles for OutFiles { + fn init(num_files: u64, settings: &Settings, is_writer_optional: bool) -> UResult { + // This object is responsible for creating the filename for each chunk + let mut filename_iterator: FilenameIterator<'_> = + FilenameIterator::new(&settings.prefix, &settings.suffix) + .map_err(|e| io::Error::new(ErrorKind::Other, format!("{e}")))?; + let mut out_files: Self = Self::new(); + for _ in 0..num_files { + let filename = filename_iterator + .next() + .ok_or_else(|| USimpleError::new(1, "output file suffixes exhausted"))?; + let maybe_writer = if is_writer_optional { + None + } else { + let instantiated = settings.instantiate_current_writer(filename.as_str(), true); + // If there was an error instantiating the writer for a file, + // it could be due to hitting the system limit of open files, + // so record it as None and let [`get_writer`] function handle closing/re-opening + // of writers as needed within system limits. + // However, for `--filter` child process writers - propagate the error, + // as working around system limits of open files for child shell processes + // is currently not supported (same as in GNU) + match instantiated { + Ok(writer) => Some(writer), + Err(e) if settings.filter.is_some() => { + return Err(e.into()); + } + Err(_) => None, + } + }; + out_files.push(OutFile { + filename, + maybe_writer, + is_new: true, + }); } + Ok(out_files) } -} -/// Generate a set of Output Files -/// This is a helper function to [`n_chunks_by_byte`], [`n_chunks_by_line`] -/// and [`n_chunks_by_line_round_robin`]. -/// Each OutFile is generated with filename, while the writer for it could be -/// optional, to be instantiated later by the calling function as needed. -/// Optional writers could happen in [`n_chunks_by_line`] -/// if `elide_empty_files` parameter is set to `true`. -fn get_out_files( - num_files: u64, - settings: &Settings, - is_writer_optional: bool, -) -> UResult> { - // This object is responsible for creating the filename for each chunk - let mut filename_iterator: FilenameIterator<'_> = - FilenameIterator::new(&settings.prefix, &settings.suffix) - .map_err(|e| io::Error::new(ErrorKind::Other, format!("{e}")))?; - let mut out_files: Vec = Vec::new(); - for _ in 0..num_files { - let filename = filename_iterator - .next() - .ok_or_else(|| USimpleError::new(1, "output file suffixes exhausted"))?; - let maybe_writer = if is_writer_optional { - None + fn get_writer( + &mut self, + idx: usize, + settings: &Settings, + ) -> UResult<&mut BufWriter>> { + if self[idx].maybe_writer.is_some() { + Ok(self[idx].maybe_writer.as_mut().unwrap()) } else { - Some(settings.instantiate_current_writer(filename.as_str())?) - }; - out_files.push(OutFile { - filename, - maybe_writer, - }); + // Writer was not instantiated upfront or was temporarily closed due to system resources constraints. + // Instantiate it and record for future use. + let maybe_writer = + settings.instantiate_current_writer(self[idx].filename.as_str(), self[idx].is_new); + if let Ok(writer) = maybe_writer { + self[idx].maybe_writer = Some(writer); + Ok(self[idx].maybe_writer.as_mut().unwrap()) + } else if settings.filter.is_some() { + // Propagate error if in `--filter` mode + Err(maybe_writer.err().unwrap().into()) + } else { + // Could have hit system limit for open files. + // Try to close one previously instantiated writer first + for (i, out_file) in self.iter_mut().enumerate() { + if i != idx && out_file.maybe_writer.is_some() { + out_file.maybe_writer.as_mut().unwrap().flush()?; + out_file.maybe_writer = None; + out_file.is_new = false; + break; + } + } + // And then try to instantiate the writer again + // If this fails - give up and propagate the error + self[idx].maybe_writer = + Some(settings.instantiate_current_writer( + self[idx].filename.as_str(), + self[idx].is_new, + )?); + Ok(self[idx].maybe_writer.as_mut().unwrap()) + } + } } - Ok(out_files) } /// Split a file or STDIN into a specific number of chunks by byte. @@ -1261,7 +1345,7 @@ where // In Kth chunk of N mode - we will write to stdout instead of to a file. let mut stdout_writer = std::io::stdout().lock(); // In N chunks mode - we will write to `num_chunks` files - let mut out_files: Vec = Vec::new(); + let mut out_files: OutFiles = OutFiles::new(); // Calculate chunk size base and modulo reminder // to be used in calculating chunk_size later on @@ -1273,7 +1357,7 @@ where // This will create each of the underlying files // or stdin pipes to child shell/command processes if in `--filter` mode if kth_chunk.is_none() { - out_files = get_out_files(num_chunks, settings, false)?; + out_files = OutFiles::init(num_chunks, settings, false)?; } for i in 1_u64..=num_chunks { @@ -1317,7 +1401,7 @@ where } None => { let idx = (i - 1) as usize; - let writer = out_files[idx].get_writer(settings)?; + let writer = out_files.get_writer(idx, settings)?; writer.write_all(buf)?; } } @@ -1387,7 +1471,7 @@ where // In Kth chunk of N mode - we will write to stdout instead of to a file. let mut stdout_writer = std::io::stdout().lock(); // In N chunks mode - we will write to `num_chunks` files - let mut out_files: Vec = Vec::new(); + let mut out_files: OutFiles = OutFiles::new(); // Calculate chunk size base and modulo reminder // to be used in calculating `num_bytes_should_be_written` later on @@ -1402,7 +1486,7 @@ where // Otherwise keep writer optional, to be instantiated later if there is data // to write for the associated chunk. if kth_chunk.is_none() { - out_files = get_out_files(num_chunks, settings, settings.elide_empty_files)?; + out_files = OutFiles::init(num_chunks, settings, settings.elide_empty_files)?; } let mut chunk_number = 1; @@ -1429,7 +1513,7 @@ where None => { // Should write into a file let idx = (chunk_number - 1) as usize; - let writer = out_files[idx].get_writer(settings)?; + let writer = out_files.get_writer(idx, settings)?; custom_write_all(bytes, writer, settings)?; } } @@ -1503,14 +1587,14 @@ where // In Kth chunk of N mode - we will write to stdout instead of to a file. let mut stdout_writer = std::io::stdout().lock(); // In N chunks mode - we will write to `num_chunks` files - let mut out_files: Vec = Vec::new(); + let mut out_files: OutFiles = OutFiles::new(); // If in N chunks mode // Create one writer for each chunk. // This will create each of the underlying files // or stdin pipes to child shell/command processes if in `--filter` mode if kth_chunk.is_none() { - out_files = get_out_files(num_chunks, settings, false)?; + out_files = OutFiles::init(num_chunks, settings, false)?; } let num_chunks: usize = num_chunks.try_into().unwrap(); @@ -1532,7 +1616,7 @@ where } } None => { - let writer = out_files[i % num_chunks].get_writer(settings)?; + let writer = out_files.get_writer(i % num_chunks, settings)?; let writer_stdin_open = custom_write_all(bytes, writer, settings)?; if !writer_stdin_open { closed_writers += 1; From 440e7b1a597091b5b0e3d81007f55ea6fbe96ee3 Mon Sep 17 00:00:00 2001 From: zhitkoff Date: Fri, 24 Nov 2023 19:10:47 -0500 Subject: [PATCH 0398/2851] split: r-chunk test compliance --- src/uu/split/src/split.rs | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/src/uu/split/src/split.rs b/src/uu/split/src/split.rs index e5f9032c947..932013ad9b3 100644 --- a/src/uu/split/src/split.rs +++ b/src/uu/split/src/split.rs @@ -1167,7 +1167,7 @@ trait ManageOutFiles { /// Each OutFile is generated with filename, while the writer for it could be /// optional, to be instantiated later by the calling function as needed. /// Optional writers could happen in the following situations: - /// * in [`n_chunks_by_line`] if `elide_empty_files` parameter is set to `true` + /// * in [`n_chunks_by_line`] and [`n_chunks_by_line_round_robin`] if `elide_empty_files` parameter is set to `true` /// * if the number of files is greater than system limit for open files fn init(num_files: u64, settings: &Settings, is_writer_optional: bool) -> UResult where @@ -1584,6 +1584,12 @@ fn n_chunks_by_line_round_robin( where R: BufRead, { + // Get the size of the input in bytes and compute the number + // of bytes per chunk. + let initial_buf = &mut Vec::new(); + let num_bytes = get_input_size(&settings.input, reader, initial_buf, &settings.io_blksize)?; + let reader = initial_buf.chain(reader); + // In Kth chunk of N mode - we will write to stdout instead of to a file. let mut stdout_writer = std::io::stdout().lock(); // In N chunks mode - we will write to `num_chunks` files @@ -1594,16 +1600,22 @@ where // This will create each of the underlying files // or stdin pipes to child shell/command processes if in `--filter` mode if kth_chunk.is_none() { - out_files = OutFiles::init(num_chunks, settings, false)?; + out_files = OutFiles::init(num_chunks, settings, settings.elide_empty_files)?; } let num_chunks: usize = num_chunks.try_into().unwrap(); let sep = settings.separator; let mut closed_writers = 0; + let mut num_bytes_written = 0; + for (i, line_result) in reader.split(sep).enumerate() { - // add separator back in at the end of the line let mut line = line_result?; - line.push(sep); + // add separator back in at the end of the line, + // since `reader.split(sep)` removes it, + // except if the last line did not end with separator character + if (num_bytes_written + line.len() as u64) < num_bytes { + line.push(sep); + } let bytes = line.as_slice(); match kth_chunk { @@ -1627,6 +1639,8 @@ where } } } + let num_line_bytes = bytes.len() as u64; + num_bytes_written += num_line_bytes; } Ok(()) From 7b2a3e236e86786f35e3c9314cb1e8916ad37247 Mon Sep 17 00:00:00 2001 From: zhitkoff Date: Fri, 24 Nov 2023 20:56:05 -0500 Subject: [PATCH 0399/2851] split: r-chunk tests and infinite input --- src/uu/split/src/split.rs | 66 ++++++++++++------------------------- tests/by-util/test_split.rs | 26 +++++++++++++-- 2 files changed, 44 insertions(+), 48 deletions(-) diff --git a/src/uu/split/src/split.rs b/src/uu/split/src/split.rs index 932013ad9b3..a837bcb21ec 100644 --- a/src/uu/split/src/split.rs +++ b/src/uu/split/src/split.rs @@ -622,7 +622,7 @@ fn custom_write_all( /// Get the size of the input file in bytes /// Used only for subset of `--number=CHUNKS` strategy, as there is a need -/// to determine input file size upfront in order to know chunk size +/// to determine input file size upfront in order to estimate the chunk size /// to be written into each of N files/chunks: /// * N split into N files based on size of input /// * K/N output Kth of N to stdout @@ -1141,23 +1141,6 @@ struct OutFile { is_new: bool, } -// impl OutFile { -// /// Get the writer for the output file. -// /// Instantiate the writer if it has not been instantiated upfront -// /// or temporarily closed to free up system resources -// fn get_writer(&mut self, settings: &Settings) -> UResult<&mut BufWriter>> { -// if self.maybe_writer.is_some() { -// Ok(self.maybe_writer.as_mut().unwrap()) -// } else { -// // Writer was not instantiated upfront or was temporarily closed due to system resources constraints. -// // Instantiate it and record for future use. -// self.maybe_writer = -// Some(settings.instantiate_current_writer(self.filename.as_str(), self.is_new)?); -// Ok(self.maybe_writer.as_mut().unwrap()) -// } -// } -// } - /// A set of output files /// Used in [`n_chunks_by_byte`], [`n_chunks_by_line`] /// and [`n_chunks_by_line_round_robin`] functions. @@ -1551,7 +1534,11 @@ where } /// Split a file or STDIN into a specific number of chunks by line, but -/// assign lines via round-robin +/// assign lines via round-robin. +/// Note: There is no need to know the size of the input upfront for this method, +/// since the lines are assigned to chunks randomly and the size of each chunk +/// does not need to be estimated. As a result, "infinite" inputs are supported +/// for this method, i.e. `yes | split -n r/10` or `yes | split -n r/3/11` /// /// In Kth chunk of N mode - writes to stdout the contents of the chunk identified by `kth_chunk` /// @@ -1584,12 +1571,6 @@ fn n_chunks_by_line_round_robin( where R: BufRead, { - // Get the size of the input in bytes and compute the number - // of bytes per chunk. - let initial_buf = &mut Vec::new(); - let num_bytes = get_input_size(&settings.input, reader, initial_buf, &settings.io_blksize)?; - let reader = initial_buf.chain(reader); - // In Kth chunk of N mode - we will write to stdout instead of to a file. let mut stdout_writer = std::io::stdout().lock(); // In N chunks mode - we will write to `num_chunks` files @@ -1606,23 +1587,20 @@ where let num_chunks: usize = num_chunks.try_into().unwrap(); let sep = settings.separator; let mut closed_writers = 0; - let mut num_bytes_written = 0; - for (i, line_result) in reader.split(sep).enumerate() { - let mut line = line_result?; - // add separator back in at the end of the line, - // since `reader.split(sep)` removes it, - // except if the last line did not end with separator character - if (num_bytes_written + line.len() as u64) < num_bytes { - line.push(sep); - } - let bytes = line.as_slice(); + let mut i = 0; + loop { + let line = &mut Vec::new(); + let num_bytes_read = reader.by_ref().read_until(sep, line)?; + // if there is nothing else to read - exit the loop + if num_bytes_read == 0 { + break; + }; + + let bytes = line.as_slice(); match kth_chunk { Some(chunk_number) => { - // The `.enumerate()` method returns index `i` starting with 0, - // but chunk number is given as a 1-indexed number, - // so compare to `chunk_number - 1` if (i % num_chunks) == (chunk_number - 1) as usize { stdout_writer.write_all(bytes)?; } @@ -1632,17 +1610,15 @@ where let writer_stdin_open = custom_write_all(bytes, writer, settings)?; if !writer_stdin_open { closed_writers += 1; - if closed_writers == num_chunks { - // all writers are closed - stop reading - break; - } } } } - let num_line_bytes = bytes.len() as u64; - num_bytes_written += num_line_bytes; + i += 1; + if closed_writers == num_chunks { + // all writers are closed - stop reading + break; + } } - Ok(()) } diff --git a/tests/by-util/test_split.rs b/tests/by-util/test_split.rs index 2c9a56bddee..acb8ab56140 100644 --- a/tests/by-util/test_split.rs +++ b/tests/by-util/test_split.rs @@ -2,11 +2,13 @@ // // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// spell-checker:ignore xzaaa sixhundredfiftyonebytes ninetyonebytes threebytes asciilowercase ghijkl mnopq rstuv wxyz fivelines twohundredfortyonebytes onehundredlines nbbbb dxen ncccc +// spell-checker:ignore xzaaa sixhundredfiftyonebytes ninetyonebytes threebytes asciilowercase ghijkl mnopq rstuv wxyz fivelines twohundredfortyonebytes onehundredlines nbbbb dxen ncccc rlimit NOFILE use crate::common::util::{AtPath, TestScenario}; use rand::{thread_rng, Rng, SeedableRng}; use regex::Regex; +#[cfg(any(target_os = "linux", target_os = "android"))] +use rlimit::Resource; #[cfg(not(windows))] use std::env; use std::path::Path; @@ -1250,10 +1252,19 @@ fn test_number_by_lines_kth_no_end_sep() { .succeeds() .stdout_only("2222\n"); new_ucmd!() - .args(&["-e", "-n", "l/8/10"]) + .args(&["-e", "-n", "l/2/2"]) .pipe_in("1\n2222\n3\n4") .succeeds() - .stdout_only("3\n"); + .stdout_only("3\n4"); +} + +#[test] +fn test_number_by_lines_rr_kth_no_end_sep() { + new_ucmd!() + .args(&["-n", "r/2/3"]) + .pipe_in("1\n2\n3\n4\n5") + .succeeds() + .stdout_only("2\n5"); } #[test] @@ -1626,6 +1637,15 @@ fn test_round_robin() { assert_eq!(at.read("xab"), "2\n4\n"); } +#[test] +#[cfg(any(target_os = "linux", target_os = "android"))] +fn test_round_robin_limited_file_descriptors() { + new_ucmd!() + .args(&["-n", "r/40", "onehundredlines.txt"]) + .limit(Resource::NOFILE, 9, 9) + .succeeds(); +} + #[test] fn test_split_invalid_input() { // Test if stdout/stderr for '--lines' option is correct From f8f63461264208ccd26842ec02f61f120c751072 Mon Sep 17 00:00:00 2001 From: zhitkoff Date: Sat, 25 Nov 2023 18:16:34 -0500 Subject: [PATCH 0400/2851] wc: fix FilesDisabled error message --- src/uu/wc/src/wc.rs | 12 ++++++++---- tests/by-util/test_wc.rs | 3 ++- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/uu/wc/src/wc.rs b/src/uu/wc/src/wc.rs index 663bbda151b..ae9b24f5d84 100644 --- a/src/uu/wc/src/wc.rs +++ b/src/uu/wc/src/wc.rs @@ -167,7 +167,7 @@ impl<'a> Inputs<'a> { None => Ok(Self::Files0From(input)), } } - (Some(_), Some(_)) => Err(WcError::FilesDisabled.into()), + (Some(mut files), Some(_)) => Err(WcError::files_disabled(files.next()).into()), } } @@ -342,8 +342,8 @@ impl TotalWhen { #[derive(Debug, Error)] enum WcError { - #[error("file operands cannot be combined with --files0-from")] - FilesDisabled, + #[error("extra operand '{extra}'\nfile operands cannot be combined with --files0-from")] + FilesDisabled { extra: Cow<'static, str> }, #[error("when reading file names from stdin, no file name of '-' allowed")] StdinReprNotAllowed, #[error("invalid zero-length file name")] @@ -365,11 +365,15 @@ impl WcError { None => Self::ZeroLengthFileName, } } + fn files_disabled(first_extra: Option<&OsString>) -> Self { + let extra = first_extra.unwrap().to_string_lossy().into_owned().into(); + Self::FilesDisabled { extra } + } } impl UError for WcError { fn usage(&self) -> bool { - matches!(self, Self::FilesDisabled) + matches!(self, Self::FilesDisabled { .. }) } } diff --git a/tests/by-util/test_wc.rs b/tests/by-util/test_wc.rs index 6417470c57f..8358a542a23 100644 --- a/tests/by-util/test_wc.rs +++ b/tests/by-util/test_wc.rs @@ -423,7 +423,8 @@ fn test_files_from_pseudo_filesystem() { #[test] fn test_files0_disabled_files_argument() { - const MSG: &str = "file operands cannot be combined with --files0-from"; + const MSG: &str = + "extra operand 'lorem_ipsum.txt'\nfile operands cannot be combined with --files0-from"; new_ucmd!() .args(&["--files0-from=files0_list.txt"]) .arg("lorem_ipsum.txt") From 04e568db7354347a103f88e6a1a0c34c74d54128 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 26 Nov 2023 00:47:15 +0000 Subject: [PATCH 0401/2851] chore(deps): update davidanson/markdownlint-cli2-action action to v14 --- .github/workflows/CICD.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index 66ee23168b8..8dfa0b1d103 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -133,7 +133,7 @@ jobs: shell: bash run: | RUSTDOCFLAGS="-Dwarnings" cargo doc ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} --no-deps --workspace --document-private-items - - uses: DavidAnson/markdownlint-cli2-action@v13 + - uses: DavidAnson/markdownlint-cli2-action@v14 with: fix: "true" globs: | From b6553edfb7ecefe54aecc21bc130e878852c9ba5 Mon Sep 17 00:00:00 2001 From: Gary Yendell Date: Sat, 25 Nov 2023 17:35:48 +0000 Subject: [PATCH 0402/2851] hashsum: Remove usage of crash! macro --- src/uu/hashsum/src/hashsum.rs | 204 +++++++++++++++++----------------- 1 file changed, 105 insertions(+), 99 deletions(-) diff --git a/src/uu/hashsum/src/hashsum.rs b/src/uu/hashsum/src/hashsum.rs index d27b09b98ed..22ef4152070 100644 --- a/src/uu/hashsum/src/hashsum.rs +++ b/src/uu/hashsum/src/hashsum.rs @@ -20,12 +20,13 @@ use std::io::{self, stdin, BufRead, BufReader, Read}; use std::iter; use std::num::ParseIntError; use std::path::Path; +use uucore::error::USimpleError; use uucore::error::{FromIo, UError, UResult}; use uucore::sum::{ Blake2b, Blake3, Digest, DigestWriter, Md5, Sha1, Sha224, Sha256, Sha384, Sha3_224, Sha3_256, Sha3_384, Sha3_512, Sha512, Shake128, Shake256, }; -use uucore::{crash, display::Quotable, show_warning}; +use uucore::{display::Quotable, show_warning}; use uucore::{format_usage, help_about, help_usage}; const NAME: &str = "hashsum"; @@ -51,28 +52,32 @@ struct Options { /// /// # Returns /// -/// Returns a tuple containing the algorithm name, the hasher instance, and the output length in bits. -/// -/// # Panics -/// -/// Panics if the length is not a multiple of 8 or if it is greater than 512. -fn create_blake2b(matches: &ArgMatches) -> (&'static str, Box, usize) { +/// Returns a UResult of a tuple containing the algorithm name, the hasher instance, and +/// the output length in bits or an Err if the length is not a multiple of 8 or if it is +/// greater than 512. +fn create_blake2b(matches: &ArgMatches) -> UResult<(&'static str, Box, usize)> { match matches.get_one::("length") { - Some(0) | None => ("BLAKE2", Box::new(Blake2b::new()) as Box, 512), + Some(0) | None => Ok(("BLAKE2", Box::new(Blake2b::new()) as Box, 512)), Some(length_in_bits) => { if *length_in_bits > 512 { - crash!(1, "Invalid length (maximum digest length is 512 bits)") + return Err(USimpleError::new( + 1, + "Invalid length (maximum digest length is 512 bits)", + )); } if length_in_bits % 8 == 0 { let length_in_bytes = length_in_bits / 8; - ( + Ok(( "BLAKE2", Box::new(Blake2b::with_output_bytes(length_in_bytes)), *length_in_bits, - ) + )) } else { - crash!(1, "Invalid length (expected a multiple of 8)") + Err(USimpleError::new( + 1, + "Invalid length (expected a multiple of 8)", + )) } } } @@ -82,38 +87,36 @@ fn create_blake2b(matches: &ArgMatches) -> (&'static str, Box, usize /// /// # Returns /// -/// Returns a tuple containing the algorithm name, the hasher instance, and the output length in bits. -/// -/// # Panics -/// -/// Panics if an unsupported output size is provided, or if the `--bits` flag is missing. -fn create_sha3(matches: &ArgMatches) -> (&'static str, Box, usize) { +/// Returns a UResult of a tuple containing the algorithm name, the hasher instance, and +/// the output length in bits or an Err if an unsupported output size is provided, or if +/// the `--bits` flag is missing. +fn create_sha3(matches: &ArgMatches) -> UResult<(&'static str, Box, usize)> { match matches.get_one::("bits") { - Some(224) => ( + Some(224) => Ok(( "SHA3-224", Box::new(Sha3_224::new()) as Box, 224, - ), - Some(256) => ( + )), + Some(256) => Ok(( "SHA3-256", Box::new(Sha3_256::new()) as Box, 256, - ), - Some(384) => ( + )), + Some(384) => Ok(( "SHA3-384", Box::new(Sha3_384::new()) as Box, 384, - ), - Some(512) => ( + )), + Some(512) => Ok(( "SHA3-512", Box::new(Sha3_512::new()) as Box, 512, - ), - Some(_) => crash!( + )), + Some(_) => Err(USimpleError::new( 1, - "Invalid output size for SHA3 (expected 224, 256, 384, or 512)" - ), - None => crash!(1, "--bits required for SHA3"), + "Invalid output size for SHA3 (expected 224, 256, 384, or 512)", + )), + None => Err(USimpleError::new(1, "--bits required for SHA3")), } } @@ -121,19 +124,16 @@ fn create_sha3(matches: &ArgMatches) -> (&'static str, Box, usize) { /// /// # Returns /// -/// Returns a tuple containing the algorithm name, the hasher instance, and the output length in bits. -/// -/// # Panics -/// -/// Panics if the `--bits` flag is missing. -fn create_shake128(matches: &ArgMatches) -> (&'static str, Box, usize) { +/// Returns a UResult of a tuple containing the algorithm name, the hasher instance, and +/// the output length in bits, or an Err if `--bits` flag is missing. +fn create_shake128(matches: &ArgMatches) -> UResult<(&'static str, Box, usize)> { match matches.get_one::("bits") { - Some(bits) => ( + Some(bits) => Ok(( "SHAKE128", Box::new(Shake128::new()) as Box, *bits, - ), - None => crash!(1, "--bits required for SHAKE-128"), + )), + None => Err(USimpleError::new(1, "--bits required for SHAKE-128")), } } @@ -141,19 +141,16 @@ fn create_shake128(matches: &ArgMatches) -> (&'static str, Box, usiz /// /// # Returns /// -/// Returns a tuple containing the algorithm name, the hasher instance, and the output length in bits. -/// -/// # Panics -/// -/// Panics if the `--bits` flag is missing. -fn create_shake256(matches: &ArgMatches) -> (&'static str, Box, usize) { +/// Returns a UResult of a tuple containing the algorithm name, the hasher instance, and +/// the output length in bits, or an Err if the `--bits` flag is missing. +fn create_shake256(matches: &ArgMatches) -> UResult<(&'static str, Box, usize)> { match matches.get_one::("bits") { - Some(bits) => ( + Some(bits) => Ok(( "SHAKE256", Box::new(Shake256::new()) as Box, *bits, - ), - None => crash!(1, "--bits required for SHAKE-256"), + )), + None => Err(USimpleError::new(1, "--bits required for SHAKE-256")), } } @@ -166,46 +163,46 @@ fn create_shake256(matches: &ArgMatches) -> (&'static str, Box, usiz /// /// # Returns /// -/// Returns a tuple containing the algorithm name, the hasher instance, and the output length in bits. +/// Returns a UResult of a tuple containing the algorithm name, the hasher instance, and +/// the output length in bits, or an Err if a matching algorithm is not found. fn detect_algo( program: &str, matches: &ArgMatches, -) -> (&'static str, Box, usize) { - let (name, alg, output_bits) = match program { - "md5sum" => ("MD5", Box::new(Md5::new()) as Box, 128), - "sha1sum" => ("SHA1", Box::new(Sha1::new()) as Box, 160), - "sha224sum" => ("SHA224", Box::new(Sha224::new()) as Box, 224), - "sha256sum" => ("SHA256", Box::new(Sha256::new()) as Box, 256), - "sha384sum" => ("SHA384", Box::new(Sha384::new()) as Box, 384), - "sha512sum" => ("SHA512", Box::new(Sha512::new()) as Box, 512), +) -> UResult<(&'static str, Box, usize)> { + match program { + "md5sum" => Ok(("MD5", Box::new(Md5::new()) as Box, 128)), + "sha1sum" => Ok(("SHA1", Box::new(Sha1::new()) as Box, 160)), + "sha224sum" => Ok(("SHA224", Box::new(Sha224::new()) as Box, 224)), + "sha256sum" => Ok(("SHA256", Box::new(Sha256::new()) as Box, 256)), + "sha384sum" => Ok(("SHA384", Box::new(Sha384::new()) as Box, 384)), + "sha512sum" => Ok(("SHA512", Box::new(Sha512::new()) as Box, 512)), "b2sum" => create_blake2b(matches), - "b3sum" => ("BLAKE3", Box::new(Blake3::new()) as Box, 256), + "b3sum" => Ok(("BLAKE3", Box::new(Blake3::new()) as Box, 256)), "sha3sum" => create_sha3(matches), - "sha3-224sum" => ( + "sha3-224sum" => Ok(( "SHA3-224", Box::new(Sha3_224::new()) as Box, 224, - ), - "sha3-256sum" => ( + )), + "sha3-256sum" => Ok(( "SHA3-256", Box::new(Sha3_256::new()) as Box, 256, - ), - "sha3-384sum" => ( + )), + "sha3-384sum" => Ok(( "SHA3-384", Box::new(Sha3_384::new()) as Box, 384, - ), - "sha3-512sum" => ( + )), + "sha3-512sum" => Ok(( "SHA3-512", Box::new(Sha3_512::new()) as Box, 512, - ), + )), "shake128sum" => create_shake128(matches), "shake256sum" => create_shake256(matches), _ => create_algorithm_from_flags(matches), - }; - (name, alg, output_bits) + } } /// Creates a hasher instance based on the command-line flags. @@ -216,80 +213,89 @@ fn detect_algo( /// /// # Returns /// -/// Returns a tuple containing the algorithm name, the hasher instance, and the output length in bits. -/// -/// # Panics -/// -/// Panics if multiple hash algorithms are specified or if a required flag is missing. +/// Returns a UResult of a tuple containing the algorithm name, the hasher instance, and +/// the output length in bits or an Err if multiple hash algorithms are specified or if a +/// required flag is missing. #[allow(clippy::cognitive_complexity)] -fn create_algorithm_from_flags(matches: &ArgMatches) -> (&'static str, Box, usize) { +fn create_algorithm_from_flags( + matches: &ArgMatches, +) -> UResult<(&'static str, Box, usize)> { let mut alg: Option> = None; let mut name: &'static str = ""; let mut output_bits = 0; - let mut set_or_crash = |n, val, bits| { + let mut set_or_err = |n, val, bits| { if alg.is_some() { - crash!(1, "You cannot combine multiple hash algorithms!"); + return Err(USimpleError::new( + 1, + "You cannot combine multiple hash algorithms!", + )); }; name = n; alg = Some(val); output_bits = bits; + + Ok(()) }; if matches.get_flag("md5") { - set_or_crash("MD5", Box::new(Md5::new()), 128); + set_or_err("MD5", Box::new(Md5::new()), 128)?; } if matches.get_flag("sha1") { - set_or_crash("SHA1", Box::new(Sha1::new()), 160); + set_or_err("SHA1", Box::new(Sha1::new()), 160)?; } if matches.get_flag("sha224") { - set_or_crash("SHA224", Box::new(Sha224::new()), 224); + set_or_err("SHA224", Box::new(Sha224::new()), 224)?; } if matches.get_flag("sha256") { - set_or_crash("SHA256", Box::new(Sha256::new()), 256); + set_or_err("SHA256", Box::new(Sha256::new()), 256)?; } if matches.get_flag("sha384") { - set_or_crash("SHA384", Box::new(Sha384::new()), 384); + set_or_err("SHA384", Box::new(Sha384::new()), 384)?; } if matches.get_flag("sha512") { - set_or_crash("SHA512", Box::new(Sha512::new()), 512); + set_or_err("SHA512", Box::new(Sha512::new()), 512)?; } if matches.get_flag("b2sum") { - set_or_crash("BLAKE2", Box::new(Blake2b::new()), 512); + set_or_err("BLAKE2", Box::new(Blake2b::new()), 512)?; } if matches.get_flag("b3sum") { - set_or_crash("BLAKE3", Box::new(Blake3::new()), 256); + set_or_err("BLAKE3", Box::new(Blake3::new()), 256)?; } if matches.get_flag("sha3") { - let (n, val, bits) = create_sha3(matches); - set_or_crash(n, val, bits); + let (n, val, bits) = create_sha3(matches)?; + set_or_err(n, val, bits)?; } if matches.get_flag("sha3-224") { - set_or_crash("SHA3-224", Box::new(Sha3_224::new()), 224); + set_or_err("SHA3-224", Box::new(Sha3_224::new()), 224)?; } if matches.get_flag("sha3-256") { - set_or_crash("SHA3-256", Box::new(Sha3_256::new()), 256); + set_or_err("SHA3-256", Box::new(Sha3_256::new()), 256)?; } if matches.get_flag("sha3-384") { - set_or_crash("SHA3-384", Box::new(Sha3_384::new()), 384); + set_or_err("SHA3-384", Box::new(Sha3_384::new()), 384)?; } if matches.get_flag("sha3-512") { - set_or_crash("SHA3-512", Box::new(Sha3_512::new()), 512); + set_or_err("SHA3-512", Box::new(Sha3_512::new()), 512)?; } if matches.get_flag("shake128") { match matches.get_one::("bits") { - Some(bits) => set_or_crash("SHAKE128", Box::new(Shake128::new()), *bits), - None => crash!(1, "--bits required for SHAKE-128"), - } + Some(bits) => set_or_err("SHAKE128", Box::new(Shake128::new()), *bits)?, + None => return Err(USimpleError::new(1, "--bits required for SHAKE-128")), + }; } if matches.get_flag("shake256") { match matches.get_one::("bits") { - Some(bits) => set_or_crash("SHAKE256", Box::new(Shake256::new()), *bits), - None => crash!(1, "--bits required for SHAKE-256"), - } + Some(bits) => set_or_err("SHAKE256", Box::new(Shake256::new()), *bits)?, + None => return Err(USimpleError::new(1, "--bits required for SHAKE-256")), + }; } - let alg = alg.unwrap_or_else(|| crash!(1, "You must specify hash algorithm!")); - (name, alg, output_bits) + let alg = match alg { + Some(a) => a, + None => return Err(USimpleError::new(1, "You must specify hash algorithm!")), + }; + + Ok((name, alg, output_bits)) } // TODO: return custom error type @@ -319,7 +325,7 @@ pub fn uumain(mut args: impl uucore::Args) -> UResult<()> { // least somewhat better from a user's perspective. let matches = command.try_get_matches_from(args)?; - let (name, algo, bits) = detect_algo(&binary_name, &matches); + let (name, algo, bits) = detect_algo(&binary_name, &matches)?; let binary = if matches.get_flag("binary") { true From 16f6a136971f79293cde5b615453eca237d9148c Mon Sep 17 00:00:00 2001 From: zhitkoff Date: Sun, 26 Nov 2023 15:14:48 -0500 Subject: [PATCH 0403/2851] wc: change where to unwrap --- src/uu/wc/src/wc.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/uu/wc/src/wc.rs b/src/uu/wc/src/wc.rs index ae9b24f5d84..83081124d1b 100644 --- a/src/uu/wc/src/wc.rs +++ b/src/uu/wc/src/wc.rs @@ -167,7 +167,9 @@ impl<'a> Inputs<'a> { None => Ok(Self::Files0From(input)), } } - (Some(mut files), Some(_)) => Err(WcError::files_disabled(files.next()).into()), + (Some(mut files), Some(_)) => { + Err(WcError::files_disabled(files.next().unwrap()).into()) + } } } @@ -365,8 +367,8 @@ impl WcError { None => Self::ZeroLengthFileName, } } - fn files_disabled(first_extra: Option<&OsString>) -> Self { - let extra = first_extra.unwrap().to_string_lossy().into_owned().into(); + fn files_disabled(first_extra: &OsString) -> Self { + let extra = first_extra.to_string_lossy().into_owned().into(); Self::FilesDisabled { extra } } } From 016ae34d50e2e6e5ec50fca6fc88ad257a4758a2 Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Sun, 12 Mar 2023 19:02:48 -0400 Subject: [PATCH 0404/2851] dd: add Settings.buffered field Add the `Settings.buffered` field to indicate whether partial output blocks should be buffered until they are complete. --- src/uu/dd/src/dd.rs | 8 +++ src/uu/dd/src/parseargs.rs | 74 +++++++++++++++------------ src/uu/dd/src/parseargs/unit_tests.rs | 1 + 3 files changed, 49 insertions(+), 34 deletions(-) diff --git a/src/uu/dd/src/dd.rs b/src/uu/dd/src/dd.rs index b79ae22da4e..7d9138791cf 100644 --- a/src/uu/dd/src/dd.rs +++ b/src/uu/dd/src/dd.rs @@ -76,6 +76,8 @@ struct Settings { oconv: OConvFlags, oflags: OFlags, status: Option, + /// Whether the output writer should buffer partial blocks until complete. + buffered: bool, } /// A timer which triggers on a given interval @@ -128,6 +130,12 @@ enum Num { Bytes(u64), } +impl Default for Num { + fn default() -> Self { + Self::Blocks(0) + } +} + impl Num { fn force_bytes_if(self, force: bool) -> Self { match self { diff --git a/src/uu/dd/src/parseargs.rs b/src/uu/dd/src/parseargs.rs index 0ff6e752c02..60ce9a6971f 100644 --- a/src/uu/dd/src/parseargs.rs +++ b/src/uu/dd/src/parseargs.rs @@ -35,41 +35,28 @@ pub enum ParseError { } /// Contains a temporary state during parsing of the arguments -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Default)] pub struct Parser { infile: Option, outfile: Option, - ibs: usize, - obs: usize, + /// The block size option specified on the command-line, if any. + bs: Option, + /// The input block size option specified on the command-line, if any. + ibs: Option, + /// The output block size option specified on the command-line, if any. + obs: Option, cbs: Option, skip: Num, seek: Num, count: Option, conv: ConvFlags, + /// Whether a data-transforming `conv` option has been specified. + is_conv_specified: bool, iflag: IFlags, oflag: OFlags, status: Option, } -impl Default for Parser { - fn default() -> Self { - Self { - ibs: 512, - obs: 512, - cbs: None, - infile: None, - outfile: None, - skip: Num::Blocks(0), - seek: Num::Blocks(0), - count: None, - conv: ConvFlags::default(), - iflag: IFlags::default(), - oflag: OFlags::default(), - status: None, - } - } -} - #[derive(Debug, Default, PartialEq, Eq)] pub struct ConvFlags { ascii: bool, @@ -212,15 +199,34 @@ impl Parser { fsync: conv.fsync, }; + // Input and output block sizes. + // + // The `bs` option takes precedence. If either is not + // provided, `ibs` and `obs` are each 512 bytes by default. + let (ibs, obs) = match self.bs { + None => (self.ibs.unwrap_or(512), self.obs.unwrap_or(512)), + Some(bs) => (bs, bs), + }; + + // Whether to buffer partial output blocks until they are completed. + // + // From the GNU `dd` documentation for the `bs=BYTES` option: + // + // > [...] if no data-transforming 'conv' option is specified, + // > input is copied to the output as soon as it's read, even if + // > it is smaller than the block size. + // + let buffered = self.bs.is_none() || self.is_conv_specified; + let skip = self .skip .force_bytes_if(self.iflag.skip_bytes) - .to_bytes(self.ibs as u64); + .to_bytes(ibs as u64); let seek = self .seek .force_bytes_if(self.oflag.seek_bytes) - .to_bytes(self.obs as u64); + .to_bytes(obs as u64); let count = self.count.map(|c| c.force_bytes_if(self.iflag.count_bytes)); @@ -230,8 +236,9 @@ impl Parser { count, iconv, oconv, - ibs: self.ibs, - obs: self.obs, + ibs, + obs, + buffered, infile: self.infile, outfile: self.outfile, iflags: self.iflag, @@ -244,18 +251,17 @@ impl Parser { match operand.split_once('=') { None => return Err(ParseError::UnrecognizedOperand(operand.to_string())), Some((k, v)) => match k { - "bs" => { - let bs = Self::parse_bytes(k, v)?; - self.ibs = bs; - self.obs = bs; - } + "bs" => self.bs = Some(Self::parse_bytes(k, v)?), "cbs" => self.cbs = Some(Self::parse_bytes(k, v)?), - "conv" => self.parse_conv_flags(v)?, + "conv" => { + self.is_conv_specified = true; + self.parse_conv_flags(v)?; + } "count" => self.count = Some(Self::parse_n(v)?), - "ibs" => self.ibs = Self::parse_bytes(k, v)?, + "ibs" => self.ibs = Some(Self::parse_bytes(k, v)?), "if" => self.infile = Some(v.to_string()), "iflag" => self.parse_input_flags(v)?, - "obs" => self.obs = Self::parse_bytes(k, v)?, + "obs" => self.obs = Some(Self::parse_bytes(k, v)?), "of" => self.outfile = Some(v.to_string()), "oflag" => self.parse_output_flags(v)?, "seek" | "oseek" => self.seek = Self::parse_n(v)?, diff --git a/src/uu/dd/src/parseargs/unit_tests.rs b/src/uu/dd/src/parseargs/unit_tests.rs index 142e49fd0ba..51b0933e926 100644 --- a/src/uu/dd/src/parseargs/unit_tests.rs +++ b/src/uu/dd/src/parseargs/unit_tests.rs @@ -358,6 +358,7 @@ fn parse_icf_tokens_remaining() { fsync: true, ..Default::default() }, + is_conv_specified: true, ..Default::default() }) ); From 5142f35f8395130dd331cc5756a0cd0466a1e74c Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Fri, 17 Mar 2023 20:55:49 -0400 Subject: [PATCH 0405/2851] dd: add BufferedOutput to buffer partial blocks --- src/uu/dd/src/bufferedoutput.rs | 201 ++++++++++++++++++++++++++++++++ src/uu/dd/src/dd.rs | 19 ++- 2 files changed, 209 insertions(+), 11 deletions(-) create mode 100644 src/uu/dd/src/bufferedoutput.rs diff --git a/src/uu/dd/src/bufferedoutput.rs b/src/uu/dd/src/bufferedoutput.rs new file mode 100644 index 00000000000..1735ae10d80 --- /dev/null +++ b/src/uu/dd/src/bufferedoutput.rs @@ -0,0 +1,201 @@ +// This file is part of the uutils coreutils package. +// +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. +// +// spell-checker:ignore wstat towrite cdefg bufferedoutput +//! Buffer partial output blocks until they are completed. +//! +//! Use the [`BufferedOutput`] struct to create a buffered form of the +//! [`Output`] writer. +use crate::{Output, WriteStat}; + +/// Buffer partial output blocks until they are completed. +/// +/// Complete blocks are written immediately to the inner [`Output`], +/// but partial blocks are stored in an internal buffer until they are +/// completed. +pub(crate) struct BufferedOutput<'a> { + /// The unbuffered inner block writer. + inner: Output<'a>, + + /// The internal buffer that stores a partial block. + /// + /// The size of this buffer is always less than the output block + /// size (that is, the value of the `obs` command-line option). + buf: Vec, +} + +impl<'a> BufferedOutput<'a> { + /// Add partial block buffering to the given block writer. + /// + /// The internal buffer size is at most the value of `obs` as + /// defined in `inner`. + pub(crate) fn new(inner: Output<'a>) -> Self { + let obs = inner.settings.obs; + Self { + inner, + buf: Vec::with_capacity(obs), + } + } + + pub(crate) fn discard_cache(&self, offset: libc::off_t, len: libc::off_t) { + self.inner.discard_cache(offset, len); + } + + /// Flush the partial block stored in the internal buffer. + pub(crate) fn flush(&mut self) -> std::io::Result { + let wstat = self.inner.write_blocks(&self.buf)?; + let n = wstat.bytes_total; + for _ in 0..n { + self.buf.remove(0); + } + Ok(wstat) + } + + /// Synchronize the inner block writer. + pub(crate) fn sync(&mut self) -> std::io::Result<()> { + self.inner.sync() + } + + /// Truncate the underlying file to the current stream position, if possible. + pub(crate) fn truncate(&mut self) -> std::io::Result<()> { + self.inner.dst.truncate() + } + + /// Write the given bytes one block at a time. + /// + /// Only complete blocks will be written. Partial blocks will be + /// buffered until enough bytes have been provided to complete a + /// block. The returned [`WriteStat`] object will include the + /// number of blocks written during execution of this function. + pub(crate) fn write_blocks(&mut self, buf: &[u8]) -> std::io::Result { + // Concatenate the old partial block with the new incoming bytes. + let towrite = [&self.buf, buf].concat(); + + // Write all complete blocks to the inner block writer. + // + // For example, if the output block size were 3, the buffered + // partial block were `b"ab"` and the new incoming bytes were + // `b"cdefg"`, then we would write blocks `b"abc"` and + // b`"def"` to the inner block writer. + let n = towrite.len(); + let rem = n % self.inner.settings.obs; + let wstat = self.inner.write_blocks(&towrite[..n - rem])?; + self.buf.clear(); + + // Buffer any remaining bytes as a partial block. + // + // Continuing the example above, the last byte `b"g"` would be + // buffered as a partial block until the next call to + // `write_blocks()`. + for byte in &towrite[n - rem..] { + self.buf.push(*byte); + } + + Ok(wstat) + } +} + +#[cfg(unix)] +#[cfg(test)] +mod tests { + use crate::bufferedoutput::BufferedOutput; + use crate::{Dest, Output, Settings}; + + #[test] + fn test_buffered_output_write_blocks_empty() { + let settings = Settings { + obs: 3, + ..Default::default() + }; + let inner = Output { + dst: Dest::Sink, + settings: &settings, + }; + let mut output = BufferedOutput::new(inner); + let wstat = output.write_blocks(&[]).unwrap(); + assert_eq!(wstat.writes_complete, 0); + assert_eq!(wstat.writes_partial, 0); + assert_eq!(wstat.bytes_total, 0); + assert_eq!(output.buf, vec![]); + } + + #[test] + fn test_buffered_output_write_blocks_partial() { + let settings = Settings { + obs: 3, + ..Default::default() + }; + let inner = Output { + dst: Dest::Sink, + settings: &settings, + }; + let mut output = BufferedOutput::new(inner); + let wstat = output.write_blocks(b"ab").unwrap(); + assert_eq!(wstat.writes_complete, 0); + assert_eq!(wstat.writes_partial, 0); + assert_eq!(wstat.bytes_total, 0); + assert_eq!(output.buf, b"ab"); + } + + #[test] + fn test_buffered_output_write_blocks_complete() { + let settings = Settings { + obs: 3, + ..Default::default() + }; + let inner = Output { + dst: Dest::Sink, + settings: &settings, + }; + let mut output = BufferedOutput::new(inner); + let wstat = output.write_blocks(b"abcd").unwrap(); + assert_eq!(wstat.writes_complete, 1); + assert_eq!(wstat.writes_partial, 0); + assert_eq!(wstat.bytes_total, 3); + assert_eq!(output.buf, b"d"); + } + + #[test] + fn test_buffered_output_write_blocks_append() { + let settings = Settings { + obs: 3, + ..Default::default() + }; + let inner = Output { + dst: Dest::Sink, + settings: &settings, + }; + let mut output = BufferedOutput { + inner, + buf: b"ab".to_vec(), + }; + let wstat = output.write_blocks(b"cdefg").unwrap(); + assert_eq!(wstat.writes_complete, 2); + assert_eq!(wstat.writes_partial, 0); + assert_eq!(wstat.bytes_total, 6); + assert_eq!(output.buf, b"g"); + } + + #[test] + fn test_buffered_output_flush() { + let settings = Settings { + obs: 10, + ..Default::default() + }; + let inner = Output { + dst: Dest::Sink, + settings: &settings, + }; + let mut output = BufferedOutput { + inner, + buf: b"abc".to_vec(), + }; + let wstat = output.flush().unwrap(); + assert_eq!(wstat.writes_complete, 0); + assert_eq!(wstat.writes_partial, 1); + assert_eq!(wstat.bytes_total, 3); + assert_eq!(output.buf, vec![]); + } +} diff --git a/src/uu/dd/src/dd.rs b/src/uu/dd/src/dd.rs index 7d9138791cf..9374ca0cd37 100644 --- a/src/uu/dd/src/dd.rs +++ b/src/uu/dd/src/dd.rs @@ -3,23 +3,20 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// spell-checker:ignore fname, ftype, tname, fpath, specfile, testfile, unspec, ifile, ofile, outfile, fullblock, urand, fileio, atoe, atoibm, behaviour, bmax, bremain, cflags, creat, ctable, ctty, datastructures, doesnt, etoa, fileout, fname, gnudd, iconvflags, iseek, nocache, noctty, noerror, nofollow, nolinks, nonblock, oconvflags, oseek, outfile, parseargs, rlen, rmax, rremain, rsofar, rstat, sigusr, wlen, wstat seekable oconv canonicalized fadvise Fadvise FADV DONTNEED ESPIPE +// spell-checker:ignore fname, ftype, tname, fpath, specfile, testfile, unspec, ifile, ofile, outfile, fullblock, urand, fileio, atoe, atoibm, behaviour, bmax, bremain, cflags, creat, ctable, ctty, datastructures, doesnt, etoa, fileout, fname, gnudd, iconvflags, iseek, nocache, noctty, noerror, nofollow, nolinks, nonblock, oconvflags, oseek, outfile, parseargs, rlen, rmax, rremain, rsofar, rstat, sigusr, wlen, wstat seekable oconv canonicalized fadvise Fadvise FADV DONTNEED ESPIPE bufferedoutput +mod blocks; +mod bufferedoutput; +mod conversion_tables; mod datastructures; -use datastructures::*; - +mod numbers; mod parseargs; -use parseargs::Parser; - -mod conversion_tables; - mod progress; -use progress::{gen_prog_updater, ProgUpdate, ReadStat, StatusLevel, WriteStat}; -mod blocks; use blocks::conv_block_unblock_helper; - -mod numbers; +use datastructures::*; +use parseargs::Parser; +use progress::{gen_prog_updater, ProgUpdate, ReadStat, StatusLevel, WriteStat}; use std::cmp; use std::env; From b383e609988a7f171643a387a81c512cab1257be Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Fri, 17 Mar 2023 22:41:10 -0400 Subject: [PATCH 0406/2851] dd: implement Add for WriteStat --- src/uu/dd/src/progress.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/uu/dd/src/progress.rs b/src/uu/dd/src/progress.rs index 4fe04cb0e67..ac7517c2c0c 100644 --- a/src/uu/dd/src/progress.rs +++ b/src/uu/dd/src/progress.rs @@ -379,6 +379,17 @@ impl std::ops::AddAssign for WriteStat { } } +impl std::ops::Add for WriteStat { + type Output = Self; + fn add(self, other: Self) -> Self { + Self { + writes_complete: self.writes_complete + other.writes_complete, + writes_partial: self.writes_partial + other.writes_partial, + bytes_total: self.bytes_total + other.bytes_total, + } + } +} + /// How much detail to report when printing transfer statistics. /// /// This corresponds to the available settings of the `status` From f343b7e964091507e9373c94da4619c962c8d23c Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Fri, 17 Mar 2023 22:41:56 -0400 Subject: [PATCH 0407/2851] dd: use read statistics for termination condition Correct the behavior of `dd` so that the termination condition of the main loop uses the number of bytes read, not the number of bytes written, when the `count` command-line option is given in bytes instead of blocks. --- src/uu/dd/src/dd.rs | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/src/uu/dd/src/dd.rs b/src/uu/dd/src/dd.rs index 9374ca0cd37..b760d98e022 100644 --- a/src/uu/dd/src/dd.rs +++ b/src/uu/dd/src/dd.rs @@ -901,7 +901,7 @@ fn dd_copy(mut i: Input, mut o: Output) -> std::io::Result<()> { // blocks to this output. Read/write statistics are updated on // each iteration and cumulative statistics are reported to // the progress reporting thread. - while below_count_limit(&i.settings.count, &rstat, &wstat) { + while below_count_limit(&i.settings.count, &rstat) { // Read a block from the input then write the block to the output. // // As an optimization, make an educated guess about the @@ -1108,16 +1108,10 @@ fn calc_loop_bsize( // Decide if the current progress is below a count=N limit or return // true if no such limit is set. -fn below_count_limit(count: &Option, rstat: &ReadStat, wstat: &WriteStat) -> bool { +fn below_count_limit(count: &Option, rstat: &ReadStat) -> bool { match count { - Some(Num::Blocks(n)) => { - let n = *n; - rstat.reads_complete + rstat.reads_partial <= n - } - Some(Num::Bytes(n)) => { - let n = (*n).try_into().unwrap(); - wstat.bytes_total <= n - } + Some(Num::Blocks(n)) => rstat.reads_complete + rstat.reads_partial < *n, + Some(Num::Bytes(n)) => rstat.bytes_total < *n, None => true, } } From ceccd2ecc61ed83d9c66ac55f82913723ca4d96e Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Fri, 17 Mar 2023 22:42:24 -0400 Subject: [PATCH 0408/2851] dd: buffer partial blocks in the output writer Add buffering of partial blocks in the output block writer until they are completed. --- src/uu/dd/src/bufferedoutput.rs | 31 +++++---- src/uu/dd/src/dd.rs | 115 +++++++++++++++++++++++++++----- tests/by-util/test_dd.rs | 78 +++++++++++++++++++++- 3 files changed, 194 insertions(+), 30 deletions(-) diff --git a/src/uu/dd/src/bufferedoutput.rs b/src/uu/dd/src/bufferedoutput.rs index 1735ae10d80..6ac3b430046 100644 --- a/src/uu/dd/src/bufferedoutput.rs +++ b/src/uu/dd/src/bufferedoutput.rs @@ -46,10 +46,8 @@ impl<'a> BufferedOutput<'a> { /// Flush the partial block stored in the internal buffer. pub(crate) fn flush(&mut self) -> std::io::Result { let wstat = self.inner.write_blocks(&self.buf)?; - let n = wstat.bytes_total; - for _ in 0..n { - self.buf.remove(0); - } + let n = wstat.bytes_total.try_into().unwrap(); + self.buf.drain(0..n); Ok(wstat) } @@ -70,8 +68,19 @@ impl<'a> BufferedOutput<'a> { /// block. The returned [`WriteStat`] object will include the /// number of blocks written during execution of this function. pub(crate) fn write_blocks(&mut self, buf: &[u8]) -> std::io::Result { - // Concatenate the old partial block with the new incoming bytes. - let towrite = [&self.buf, buf].concat(); + // Split the incoming buffer into two parts: the bytes to write + // and the bytes to buffer for next time. + // + // If `buf` does not include enough bytes to form a full block, + // just buffer the whole thing and write zero blocks. + let n = self.buf.len() + buf.len(); + let rem = n % self.inner.settings.obs; + let i = buf.len().saturating_sub(rem); + let (to_write, to_buffer) = buf.split_at(i); + + // Concatenate the old partial block with the new bytes to form + // some number of complete blocks. + self.buf.extend_from_slice(to_write); // Write all complete blocks to the inner block writer. // @@ -79,19 +88,15 @@ impl<'a> BufferedOutput<'a> { // partial block were `b"ab"` and the new incoming bytes were // `b"cdefg"`, then we would write blocks `b"abc"` and // b`"def"` to the inner block writer. - let n = towrite.len(); - let rem = n % self.inner.settings.obs; - let wstat = self.inner.write_blocks(&towrite[..n - rem])?; - self.buf.clear(); + let wstat = self.inner.write_blocks(&self.buf)?; // Buffer any remaining bytes as a partial block. // // Continuing the example above, the last byte `b"g"` would be // buffered as a partial block until the next call to // `write_blocks()`. - for byte in &towrite[n - rem..] { - self.buf.push(*byte); - } + self.buf.clear(); + self.buf.extend_from_slice(to_buffer); Ok(wstat) } diff --git a/src/uu/dd/src/dd.rs b/src/uu/dd/src/dd.rs index b760d98e022..645c2496766 100644 --- a/src/uu/dd/src/dd.rs +++ b/src/uu/dd/src/dd.rs @@ -13,6 +13,7 @@ mod numbers; mod parseargs; mod progress; +use crate::bufferedoutput::BufferedOutput; use blocks::conv_block_unblock_helper; use datastructures::*; use parseargs::Parser; @@ -801,6 +802,68 @@ impl<'a> Output<'a> { Ok(()) } } + + /// Truncate the underlying file to the current stream position, if possible. + fn truncate(&mut self) -> std::io::Result<()> { + self.dst.truncate() + } +} + +/// The block writer either with or without partial block buffering. +enum BlockWriter<'a> { + /// Block writer with partial block buffering. + /// + /// Partial blocks are buffered until completed. + Buffered(BufferedOutput<'a>), + + /// Block writer without partial block buffering. + /// + /// Partial blocks are written immediately. + Unbuffered(Output<'a>), +} + +impl<'a> BlockWriter<'a> { + fn discard_cache(&self, offset: libc::off_t, len: libc::off_t) { + match self { + Self::Unbuffered(o) => o.discard_cache(offset, len), + Self::Buffered(o) => o.discard_cache(offset, len), + } + } + + fn flush(&mut self) -> io::Result { + match self { + Self::Unbuffered(_) => Ok(WriteStat::default()), + Self::Buffered(o) => o.flush(), + } + } + + fn sync(&mut self) -> io::Result<()> { + match self { + Self::Unbuffered(o) => o.sync(), + Self::Buffered(o) => o.sync(), + } + } + + /// Truncate the file to the final cursor location. + fn truncate(&mut self) { + // Calling `set_len()` may result in an error (for example, + // when calling it on `/dev/null`), but we don't want to + // terminate the process when that happens. Instead, we + // suppress the error by calling `Result::ok()`. This matches + // the behavior of GNU `dd` when given the command-line + // argument `of=/dev/null`. + match self { + Self::Unbuffered(o) => o.truncate().ok(), + Self::Buffered(o) => o.truncate().ok(), + }; + } + + fn write_blocks(&mut self, buf: &[u8]) -> std::io::Result { + match self { + Self::Unbuffered(o) => o.write_blocks(buf), + Self::Buffered(o) => o.write_blocks(buf), + } + } } /// Copy the given input data to this output, consuming both. @@ -814,7 +877,7 @@ impl<'a> Output<'a> { /// /// If there is a problem reading from the input or writing to /// this output. -fn dd_copy(mut i: Input, mut o: Output) -> std::io::Result<()> { +fn dd_copy(mut i: Input, o: Output) -> std::io::Result<()> { // The read and write statistics. // // These objects are counters, initialized to zero. After each @@ -851,6 +914,9 @@ fn dd_copy(mut i: Input, mut o: Output) -> std::io::Result<()> { let (prog_tx, rx) = mpsc::channel(); let output_thread = thread::spawn(gen_prog_updater(rx, i.settings.status)); + // Whether to truncate the output file after all blocks have been written. + let truncate = !o.settings.oconv.notrunc; + // Optimization: if no blocks are to be written, then don't // bother allocating any buffers. if let Some(Num::Blocks(0) | Num::Bytes(0)) = i.settings.count { @@ -875,7 +941,15 @@ fn dd_copy(mut i: Input, mut o: Output) -> std::io::Result<()> { let len = o.dst.len()?.try_into().unwrap(); o.discard_cache(offset, len); } - return finalize(&mut o, rstat, wstat, start, &prog_tx, output_thread); + return finalize( + BlockWriter::Unbuffered(o), + rstat, + wstat, + start, + &prog_tx, + output_thread, + truncate, + ); }; // Create a common buffer with a capacity of the block size. @@ -895,6 +969,16 @@ fn dd_copy(mut i: Input, mut o: Output) -> std::io::Result<()> { let mut read_offset = 0; let mut write_offset = 0; + let input_nocache = i.settings.iflags.nocache; + let output_nocache = o.settings.oflags.nocache; + + // Add partial block buffering, if needed. + let mut o = if o.settings.buffered { + BlockWriter::Buffered(BufferedOutput::new(o)) + } else { + BlockWriter::Unbuffered(o) + }; + // The main read/write loop. // // Each iteration reads blocks from the input and writes @@ -919,7 +1003,7 @@ fn dd_copy(mut i: Input, mut o: Output) -> std::io::Result<()> { // // TODO Better error handling for overflowing `offset` and `len`. let read_len = rstat_update.bytes_total; - if i.settings.iflags.nocache { + if input_nocache { let offset = read_offset.try_into().unwrap(); let len = read_len.try_into().unwrap(); i.discard_cache(offset, len); @@ -931,7 +1015,7 @@ fn dd_copy(mut i: Input, mut o: Output) -> std::io::Result<()> { // // TODO Better error handling for overflowing `offset` and `len`. let write_len = wstat_update.bytes_total; - if o.settings.oflags.nocache { + if output_nocache { let offset = write_offset.try_into().unwrap(); let len = write_len.try_into().unwrap(); o.discard_cache(offset, len); @@ -951,34 +1035,33 @@ fn dd_copy(mut i: Input, mut o: Output) -> std::io::Result<()> { prog_tx.send(prog_update).unwrap_or(()); } } - finalize(&mut o, rstat, wstat, start, &prog_tx, output_thread) + finalize(o, rstat, wstat, start, &prog_tx, output_thread, truncate) } /// Flush output, print final stats, and join with the progress thread. fn finalize( - output: &mut Output, + mut output: BlockWriter, rstat: ReadStat, wstat: WriteStat, start: Instant, prog_tx: &mpsc::Sender, output_thread: thread::JoinHandle, + truncate: bool, ) -> std::io::Result<()> { - // Flush the output, if configured to do so. + // Flush the output in case a partial write has been buffered but + // not yet written. + let wstat_update = output.flush()?; + + // Sync the output, if configured to do so. output.sync()?; // Truncate the file to the final cursor location. - // - // Calling `set_len()` may result in an error (for example, - // when calling it on `/dev/null`), but we don't want to - // terminate the process when that happens. Instead, we - // suppress the error by calling `Result::ok()`. This matches - // the behavior of GNU `dd` when given the command-line - // argument `of=/dev/null`. - if !output.settings.oconv.notrunc { - output.dst.truncate().ok(); + if truncate { + output.truncate(); } // Print the final read/write statistics. + let wstat = wstat + wstat_update; let prog_update = ProgUpdate::new(rstat, wstat, start.elapsed(), true); prog_tx.send(prog_update).unwrap_or(()); // Wait for the output thread to finish diff --git a/tests/by-util/test_dd.rs b/tests/by-util/test_dd.rs index d5ac8dc801c..a4c70097c8b 100644 --- a/tests/by-util/test_dd.rs +++ b/tests/by-util/test_dd.rs @@ -2,7 +2,7 @@ // // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// spell-checker:ignore fname, tname, fpath, specfile, testfile, unspec, ifile, ofile, outfile, fullblock, urand, fileio, atoe, atoibm, availible, behaviour, bmax, bremain, btotal, cflags, creat, ctable, ctty, datastructures, doesnt, etoa, fileout, fname, gnudd, iconvflags, iseek, nocache, noctty, noerror, nofollow, nolinks, nonblock, oconvflags, oseek, outfile, parseargs, rlen, rmax, rposition, rremain, rsofar, rstat, sigusr, sigval, wlen, wstat abcdefghijklm abcdefghi nabcde nabcdefg abcdefg +// spell-checker:ignore fname, tname, fpath, specfile, testfile, unspec, ifile, ofile, outfile, fullblock, urand, fileio, atoe, atoibm, availible, behaviour, bmax, bremain, btotal, cflags, creat, ctable, ctty, datastructures, doesnt, etoa, fileout, fname, gnudd, iconvflags, iseek, nocache, noctty, noerror, nofollow, nolinks, nonblock, oconvflags, oseek, outfile, parseargs, rlen, rmax, rposition, rremain, rsofar, rstat, sigusr, sigval, wlen, wstat abcdefghijklm abcdefghi nabcde nabcdefg abcdefg fifoname #[cfg(unix)] use crate::common::util::run_ucmd_as_root_with_stdin_stdout; @@ -15,6 +15,8 @@ use regex::Regex; use std::fs::{File, OpenOptions}; use std::io::{BufReader, Read, Write}; use std::path::PathBuf; +#[cfg(all(unix, not(target_os = "macos"), not(target_os = "freebsd")))] +use std::process::{Command, Stdio}; #[cfg(not(windows))] use std::thread::sleep; #[cfg(not(windows))] @@ -1582,3 +1584,77 @@ fn test_seek_past_dev() { print!("TEST SKIPPED"); } } + +#[test] +#[cfg(all(unix, not(target_os = "macos"), not(target_os = "freebsd")))] +fn test_reading_partial_blocks_from_fifo() { + // Create the FIFO. + let ts = TestScenario::new(util_name!()); + let at = ts.fixtures.clone(); + at.mkfifo("fifo"); + let fifoname = at.plus_as_string("fifo"); + + // Start a `dd` process that reads from the fifo (so it will wait + // until the writer process starts). + let mut reader_command = Command::new(TESTS_BINARY); + let child = reader_command + .args(["dd", "ibs=3", "obs=3", &format!("if={}", fifoname)]) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .unwrap(); + + // Start different processes to write to the FIFO, with a small + // pause in between. + let mut writer_command = Command::new("sh"); + writer_command + .args([ + "-c", + &format!("(printf \"ab\"; sleep 0.1; printf \"cd\") > {}", fifoname), + ]) + .spawn() + .unwrap(); + + let output = child.wait_with_output().unwrap(); + assert_eq!(output.stdout, b"abcd"); + let expected = b"0+2 records in\n1+1 records out\n4 bytes copied"; + assert!(output.stderr.starts_with(expected)); +} + +#[test] +#[cfg(all(unix, not(target_os = "macos"), not(target_os = "freebsd")))] +fn test_reading_partial_blocks_from_fifo_unbuffered() { + // Create the FIFO. + let ts = TestScenario::new(util_name!()); + let at = ts.fixtures.clone(); + at.mkfifo("fifo"); + let fifoname = at.plus_as_string("fifo"); + + // Start a `dd` process that reads from the fifo (so it will wait + // until the writer process starts). + // + // `bs=N` takes precedence over `ibs=N` and `obs=N`. + let mut reader_command = Command::new(TESTS_BINARY); + let child = reader_command + .args(["dd", "bs=3", "ibs=1", "obs=1", &format!("if={}", fifoname)]) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .unwrap(); + + // Start different processes to write to the FIFO, with a small + // pause in between. + let mut writer_command = Command::new("sh"); + writer_command + .args([ + "-c", + &format!("(printf \"ab\"; sleep 0.1; printf \"cd\") > {}", fifoname), + ]) + .spawn() + .unwrap(); + + let output = child.wait_with_output().unwrap(); + assert_eq!(output.stdout, b"abcd"); + let expected = b"0+2 records in\n0+2 records out\n4 bytes copied"; + assert!(output.stderr.starts_with(expected)); +} From 8eb66ab7ea93de366165f45127206da04cdb342d Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Mon, 27 Nov 2023 11:50:55 +0100 Subject: [PATCH 0409/2851] printf: remove whitespace, remove redundant spelling ignore and revert matching on result --- src/uu/printf/src/printf.rs | 17 ++++++----------- src/uu/seq/src/number.rs | 2 +- src/uu/seq/src/numberparse.rs | 2 +- src/uu/seq/src/seq.rs | 2 +- .../src/lib/features/format/num_format.rs | 1 - 5 files changed, 9 insertions(+), 15 deletions(-) diff --git a/src/uu/printf/src/printf.rs b/src/uu/printf/src/printf.rs index cfb0315cfb0..663411b8952 100644 --- a/src/uu/printf/src/printf.rs +++ b/src/uu/printf/src/printf.rs @@ -10,9 +10,9 @@ use std::io::stdout; use std::ops::ControlFlow; use clap::{crate_version, Arg, ArgAction, Command}; -use uucore::error::{UError, UResult, UUsageError}; +use uucore::error::{UResult, UUsageError}; use uucore::format::{parse_spec_and_escape, FormatArgument}; -use uucore::{format_usage, help_about, help_section, help_usage, show}; +use uucore::{format_usage, help_about, help_section, help_usage}; const VERSION: &str = "version"; const HELP: &str = "help"; @@ -49,15 +49,10 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { while args.peek().is_some() { for item in parse_spec_and_escape(format_string.as_ref()) { - match item { - Ok(item) => { - match item.write(stdout(), &mut args)? { - ControlFlow::Continue(()) => {} - ControlFlow::Break(()) => return Ok(()), - }; - } - Err(e) => show!(e), - } + match item?.write(stdout(), &mut args)? { + ControlFlow::Continue(()) => {} + ControlFlow::Break(()) => return Ok(()), + }; } } Ok(()) diff --git a/src/uu/seq/src/number.rs b/src/uu/seq/src/number.rs index 182431a9210..314c842ba15 100644 --- a/src/uu/seq/src/number.rs +++ b/src/uu/seq/src/number.rs @@ -2,7 +2,7 @@ // // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// spell-checker:ignore extendedbigdecimal extendedbigint +// spell-checker:ignore extendedbigdecimal use num_traits::Zero; use crate::extendedbigdecimal::ExtendedBigDecimal; diff --git a/src/uu/seq/src/numberparse.rs b/src/uu/seq/src/numberparse.rs index a82d1e88776..df7c1f7d1dd 100644 --- a/src/uu/seq/src/numberparse.rs +++ b/src/uu/seq/src/numberparse.rs @@ -2,7 +2,7 @@ // // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// spell-checker:ignore extendedbigdecimal extendedbigint bigdecimal numberparse +// spell-checker:ignore extendedbigdecimal bigdecimal numberparse //! Parsing numbers for use in `seq`. //! //! This module provides an implementation of [`FromStr`] for the diff --git a/src/uu/seq/src/seq.rs b/src/uu/seq/src/seq.rs index 05338864545..33b7636edbc 100644 --- a/src/uu/seq/src/seq.rs +++ b/src/uu/seq/src/seq.rs @@ -2,7 +2,7 @@ // // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// spell-checker:ignore (ToDO) istr chiter argptr ilen extendedbigdecimal extendedbigint numberparse +// spell-checker:ignore (ToDO) extendedbigdecimal numberparse use std::io::{stdout, ErrorKind, Write}; use clap::{crate_version, Arg, ArgAction, Command}; diff --git a/src/uucore/src/lib/features/format/num_format.rs b/src/uucore/src/lib/features/format/num_format.rs index 6fd177d1325..51f3336cf61 100644 --- a/src/uucore/src/lib/features/format/num_format.rs +++ b/src/uucore/src/lib/features/format/num_format.rs @@ -28,7 +28,6 @@ pub enum UnsignedIntVariant { } #[derive(Clone, Copy, Debug)] - pub enum FloatVariant { Decimal, Scientific, From 83784b2d96bc4b68a67186ac57014822a14034d7 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Mon, 27 Nov 2023 11:37:39 +0100 Subject: [PATCH 0410/2851] expr: refactor AST and parsing --- src/uu/expr/src/expr.rs | 87 ++-- src/uu/expr/src/syntax_tree.rs | 924 ++++++++++++++++----------------- src/uu/expr/src/tokens.rs | 147 ------ 3 files changed, 494 insertions(+), 664 deletions(-) delete mode 100644 src/uu/expr/src/tokens.rs diff --git a/src/uu/expr/src/expr.rs b/src/uu/expr/src/expr.rs index 909c4c37653..c271f0935fd 100644 --- a/src/uu/expr/src/expr.rs +++ b/src/uu/expr/src/expr.rs @@ -3,14 +3,19 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. +use std::fmt::Display; + use clap::{crate_version, Arg, ArgAction, Command}; +use syntax_tree::AstNode; use uucore::{ - error::{UResult, USimpleError, UUsageError}, + display::Quotable, + error::{UError, UResult}, format_usage, help_about, help_section, help_usage, }; +use crate::syntax_tree::is_truthy; + mod syntax_tree; -mod tokens; mod options { pub const VERSION: &str = "version"; @@ -18,6 +23,51 @@ mod options { pub const EXPRESSION: &str = "expression"; } +pub type ExprResult = Result; + +#[derive(Debug, PartialEq, Eq)] +pub enum ExprError { + UnexpectedArgument(String), + MissingArgument(String), + NonIntegerArgument, + MissingOperand, + DivisionByZero, + InvalidRegexExpression, + ExpectedClosingBraceAfter(String), +} + +impl Display for ExprError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::UnexpectedArgument(s) => { + write!(f, "syntax error: unexpected argument {}", s.quote()) + } + Self::MissingArgument(s) => { + write!(f, "syntax error: missing argument after {}", s.quote()) + } + Self::NonIntegerArgument => write!(f, "non-integer argument"), + Self::MissingOperand => write!(f, "missing operand"), + Self::DivisionByZero => write!(f, "division by zero"), + Self::InvalidRegexExpression => write!(f, "Invalid regex expression"), + Self::ExpectedClosingBraceAfter(s) => { + write!(f, "expected ')' after {}", s.quote()) + } + } + } +} + +impl std::error::Error for ExprError {} + +impl UError for ExprError { + fn code(&self) -> i32 { + 2 + } + + fn usage(&self) -> bool { + *self == Self::MissingOperand + } +} + pub fn uu_app() -> Command { Command::new(uucore::util_name()) .version(crate_version!()) @@ -53,36 +103,15 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { // For expr utility we do not want getopts. // The following usage should work without escaping hyphens: `expr -15 = 1 + 2 \* \( 3 - -4 \)` let matches = uu_app().try_get_matches_from(args)?; - let token_strings = matches + let token_strings: Vec<&str> = matches .get_many::(options::EXPRESSION) .map(|v| v.into_iter().map(|s| s.as_ref()).collect::>()) .unwrap_or_default(); - if token_strings.is_empty() { - return Err(UUsageError::new(2, "missing operand")); - } - - match process_expr(&token_strings[..]) { - Ok(expr_result) => print_expr_ok(&expr_result), - Err(expr_error) => Err(USimpleError::new(2, &expr_error)), + let res = AstNode::parse(&token_strings)?.eval()?; + println!("{res}"); + if !is_truthy(&res) { + return Err(1.into()); } -} - -fn process_expr(token_strings: &[&str]) -> Result { - let maybe_tokens = tokens::strings_to_tokens(token_strings); - let maybe_ast = syntax_tree::tokens_to_ast(maybe_tokens); - evaluate_ast(maybe_ast) -} - -fn print_expr_ok(expr_result: &str) -> UResult<()> { - println!("{expr_result}"); - if expr_result.parse::() == Ok(0) || expr_result.is_empty() { - Err(1.into()) - } else { - Ok(()) - } -} - -fn evaluate_ast(maybe_ast: Result, String>) -> Result { - maybe_ast.and_then(|ast| ast.evaluate()) + Ok(()) } diff --git a/src/uu/expr/src/syntax_tree.rs b/src/uu/expr/src/syntax_tree.rs index 2260b2e2186..f81f1da1ec4 100644 --- a/src/uu/expr/src/syntax_tree.rs +++ b/src/uu/expr/src/syntax_tree.rs @@ -3,574 +3,522 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -//! -//! Here we employ shunting-yard algorithm for building AST from tokens according to operators' precedence and associative-ness. -//! * `` -//! - // spell-checker:ignore (ToDO) ints paren prec multibytes use num_bigint::BigInt; -use num_traits::Zero; use onig::{Regex, RegexOptions, Syntax}; -use uucore::display::Quotable; -use crate::tokens::Token; +use crate::{ExprError, ExprResult}; -type TokenStack = Vec<(usize, Token)>; -pub type OperandsList = Vec>; +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum BinOp { + Relation(RelationOp), + Numeric(NumericOp), + String(StringOp), +} -#[derive(Debug)] -pub enum AstNode { - Leaf { - token_idx: usize, - value: String, - }, - Node { - token_idx: usize, - op_type: String, - operands: OperandsList, - }, +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum RelationOp { + Lt, + Leq, + Eq, + Neq, + Gt, + Geq, } -impl AstNode { - fn debug_dump(&self) { - self.debug_dump_impl(1); - } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum NumericOp { + Add, + Sub, + Mul, + Div, + Mod, +} - fn debug_dump_impl(&self, depth: usize) { - for _ in 0..depth { - print!("\t",); - } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum StringOp { + Match, + Index, + And, + Or, +} + +impl BinOp { + fn eval(&self, left: &AstNode, right: &AstNode) -> ExprResult { match self { - Self::Leaf { token_idx, value } => println!( - "Leaf( {} ) at #{} ( evaluate -> {:?} )", - value, - token_idx, - self.evaluate() - ), - Self::Node { - token_idx, - op_type, - operands, - } => { - println!( - "Node( {} ) at #{} ( evaluate -> {:?} )", - op_type, - token_idx, - self.evaluate() - ); - for operand in operands { - operand.debug_dump_impl(depth + 1); - } - } + Self::Relation(op) => op.eval(left, right), + Self::Numeric(op) => op.eval(left, right), + Self::String(op) => op.eval(left, right), } } +} - fn new_node(token_idx: usize, op_type: &str, operands: OperandsList) -> Box { - Box::new(Self::Node { - token_idx, - op_type: op_type.into(), - operands, - }) - } - - fn new_leaf(token_idx: usize, value: &str) -> Box { - Box::new(Self::Leaf { - token_idx, - value: value.into(), - }) +impl RelationOp { + fn eval(&self, a: &AstNode, b: &AstNode) -> ExprResult { + let a = a.eval()?; + let b = b.eval()?; + let b = if let (Ok(a), Ok(b)) = (a.parse::(), b.parse::()) { + match self { + Self::Lt => a < b, + Self::Leq => a <= b, + Self::Eq => a == b, + Self::Neq => a != b, + Self::Gt => a > b, + Self::Geq => a >= b, + } + } else { + // These comparisons should be using locale settings + match self { + Self::Lt => a < b, + Self::Leq => a <= b, + Self::Eq => a == b, + Self::Neq => a != b, + Self::Gt => a > b, + Self::Geq => a >= b, + } + }; + if b { + Ok("1".into()) + } else { + Ok("0".into()) + } } +} - pub fn evaluate(&self) -> Result { - match self { - Self::Leaf { value, .. } => Ok(value.clone()), - Self::Node { op_type, .. } => match self.operand_values() { - Err(reason) => Err(reason), - Ok(operand_values) => match op_type.as_ref() { - "+" => { - infix_operator_two_ints(|a: BigInt, b: BigInt| Ok(a + b), &operand_values) - } - "-" => { - infix_operator_two_ints(|a: BigInt, b: BigInt| Ok(a - b), &operand_values) - } - "*" => { - infix_operator_two_ints(|a: BigInt, b: BigInt| Ok(a * b), &operand_values) - } - "/" => infix_operator_two_ints( - |a: BigInt, b: BigInt| { - if b.is_zero() { - Err("division by zero".to_owned()) - } else { - Ok(a / b) - } - }, - &operand_values, - ), - "%" => infix_operator_two_ints( - |a: BigInt, b: BigInt| { - if b.is_zero() { - Err("division by zero".to_owned()) - } else { - Ok(a % b) - } - }, - &operand_values, - ), - "=" => infix_operator_two_ints_or_two_strings( - |a: BigInt, b: BigInt| Ok(bool_as_int(a == b)), - |a: &String, b: &String| Ok(bool_as_string(a == b)), - &operand_values, - ), - "!=" => infix_operator_two_ints_or_two_strings( - |a: BigInt, b: BigInt| Ok(bool_as_int(a != b)), - |a: &String, b: &String| Ok(bool_as_string(a != b)), - &operand_values, - ), - "<" => infix_operator_two_ints_or_two_strings( - |a: BigInt, b: BigInt| Ok(bool_as_int(a < b)), - |a: &String, b: &String| Ok(bool_as_string(a < b)), - &operand_values, - ), - ">" => infix_operator_two_ints_or_two_strings( - |a: BigInt, b: BigInt| Ok(bool_as_int(a > b)), - |a: &String, b: &String| Ok(bool_as_string(a > b)), - &operand_values, - ), - "<=" => infix_operator_two_ints_or_two_strings( - |a: BigInt, b: BigInt| Ok(bool_as_int(a <= b)), - |a: &String, b: &String| Ok(bool_as_string(a <= b)), - &operand_values, - ), - ">=" => infix_operator_two_ints_or_two_strings( - |a: BigInt, b: BigInt| Ok(bool_as_int(a >= b)), - |a: &String, b: &String| Ok(bool_as_string(a >= b)), - &operand_values, - ), - "|" => Ok(infix_operator_or(&operand_values)), - "&" => Ok(infix_operator_and(&operand_values)), - ":" | "match" => operator_match(&operand_values), - "length" => Ok(prefix_operator_length(&operand_values)), - "index" => Ok(prefix_operator_index(&operand_values)), - "substr" => Ok(prefix_operator_substr(&operand_values)), - - _ => Err(format!("operation not implemented: {op_type}")), - }, +impl NumericOp { + fn eval(&self, left: &AstNode, right: &AstNode) -> ExprResult { + let a: BigInt = left + .eval()? + .parse() + .map_err(|_| ExprError::NonIntegerArgument)?; + let b: BigInt = right + .eval()? + .parse() + .map_err(|_| ExprError::NonIntegerArgument)?; + Ok(match self { + Self::Add => a + b, + Self::Sub => a - b, + Self::Mul => a * b, + Self::Div => match a.checked_div(&b) { + Some(x) => x, + None => return Err(ExprError::DivisionByZero), }, + Self::Mod => { + if a.checked_div(&b).is_none() { + return Err(ExprError::DivisionByZero); + }; + a % b + } } + .to_string()) } +} - pub fn operand_values(&self) -> Result, String> { - if let Self::Node { - operands, op_type, .. - } = self - { - let mut out = Vec::with_capacity(operands.len()); - let mut operands = operands.iter(); - - if let Some(value) = operands.next() { - let value = value.evaluate()?; - out.push(value.clone()); - // short-circuit evaluation for `|` and `&` - // push dummy to pass `assert!(values.len() == 2);` - match op_type.as_ref() { - "|" => { - if value_as_bool(&value) { - out.push(String::from("dummy")); - return Ok(out); - } - } - "&" => { - if !value_as_bool(&value) { - out.push(String::from("dummy")); - return Ok(out); +impl StringOp { + fn eval(&self, left: &AstNode, right: &AstNode) -> ExprResult { + match self { + Self::Or => { + let left = left.eval()?; + if is_truthy(&left) { + return Ok(left); + } + let right = right.eval()?; + if is_truthy(&right) { + return Ok(right); + } + Ok("0".into()) + } + Self::And => { + let left = left.eval()?; + if !is_truthy(&left) { + return Ok("0".into()); + } + let right = right.eval()?; + if !is_truthy(&right) { + return Ok("0".into()); + } + Ok(left) + } + Self::Match => { + let left = left.eval()?; + let right = right.eval()?; + let re_string = format!("^{}", &right); + let re = Regex::with_options( + &re_string, + RegexOptions::REGEX_OPTION_NONE, + Syntax::grep(), + ) + .map_err(|_| ExprError::InvalidRegexExpression)?; + Ok(if re.captures_len() > 0 { + re.captures(&left) + .map(|captures| captures.at(1).unwrap()) + .unwrap_or("") + .to_string() + } else { + re.find(&left) + .map_or("0".to_string(), |(start, end)| (end - start).to_string()) + }) + } + Self::Index => { + let left = left.eval()?; + let right = right.eval()?; + for (current_idx, ch_h) in left.chars().enumerate() { + for ch_n in right.chars() { + if ch_n == ch_h { + return Ok((current_idx + 1).to_string()); } } - _ => {} } + Ok("0".to_string()) } - - for operand in operands { - let value = operand.evaluate()?; - out.push(value); - } - Ok(out) - } else { - panic!("Invoked .operand_values(&self) not with ASTNode::Node") } } } -pub fn tokens_to_ast( - maybe_tokens: Result, String>, -) -> Result, String> { - maybe_tokens.and_then(|tokens| { - let mut out_stack: TokenStack = Vec::new(); - let mut op_stack: TokenStack = Vec::new(); - - for (token_idx, token) in tokens { - push_token_to_either_stack(token_idx, &token, &mut out_stack, &mut op_stack)?; - } - move_rest_of_ops_to_out(&mut out_stack, &mut op_stack)?; - assert!(op_stack.is_empty()); - - maybe_dump_rpn(&out_stack); - let result = ast_from_rpn(&mut out_stack); - if out_stack.is_empty() { - maybe_dump_ast(&result); - result - } else { - Err( - "syntax error (first RPN token does not represent the root of the expression AST)" - .to_owned(), - ) - } - }) +/// Precedence for infix binary operators +const PRECEDENCE: &[&[(&str, BinOp)]] = &[ + &[("|", BinOp::String(StringOp::Or))], + &[("&", BinOp::String(StringOp::And))], + &[ + ("<", BinOp::Relation(RelationOp::Lt)), + ("<=", BinOp::Relation(RelationOp::Leq)), + ("=", BinOp::Relation(RelationOp::Eq)), + ("!=", BinOp::Relation(RelationOp::Neq)), + (">=", BinOp::Relation(RelationOp::Geq)), + (">", BinOp::Relation(RelationOp::Gt)), + ], + &[ + ("+", BinOp::Numeric(NumericOp::Add)), + ("-", BinOp::Numeric(NumericOp::Sub)), + ], + &[ + ("*", BinOp::Numeric(NumericOp::Mul)), + ("/", BinOp::Numeric(NumericOp::Div)), + ("%", BinOp::Numeric(NumericOp::Mod)), + ], + &[(":", BinOp::String(StringOp::Match))], +]; + +#[derive(Debug, PartialEq, Eq)] +pub enum AstNode { + Leaf { + value: String, + }, + BinOp { + op_type: BinOp, + left: Box, + right: Box, + }, + Substr { + string: Box, + pos: Box, + length: Box, + }, + Length { + string: Box, + }, } -fn maybe_dump_ast(result: &Result, String>) { - use std::env; - if let Ok(debug_var) = env::var("EXPR_DEBUG_AST") { - if debug_var == "1" { - println!("EXPR_DEBUG_AST"); - match result { - Ok(ast) => ast.debug_dump(), - Err(reason) => println!("\terr: {reason:?}"), - } - } +impl AstNode { + pub fn parse(input: &[&str]) -> ExprResult { + Parser::new(input).parse() } -} -#[allow(clippy::ptr_arg)] -fn maybe_dump_rpn(rpn: &TokenStack) { - use std::env; - if let Ok(debug_var) = env::var("EXPR_DEBUG_RPN") { - if debug_var == "1" { - println!("EXPR_DEBUG_RPN"); - for token in rpn { - println!("\t{token:?}"); + pub fn eval(&self) -> ExprResult { + match self { + Self::Leaf { value } => Ok(value.into()), + Self::BinOp { + op_type, + left, + right, + } => op_type.eval(left, right), + Self::Substr { + string, + pos, + length, + } => { + let string = string.eval()?; + + // The GNU docs say: + // + // > If either position or length is negative, zero, or + // > non-numeric, returns the null string. + // + // So we coerce errors into 0 to make that the only case we + // have to care about. + let pos: usize = pos.eval()?.parse().unwrap_or(0); + let length: usize = length.eval()?.parse().unwrap_or(0); + + let (Some(pos), Some(_)) = (pos.checked_sub(1), length.checked_sub(1)) else { + return Ok(String::new()); + }; + + Ok(string.chars().skip(pos).take(length).collect()) } + Self::Length { string } => Ok(string.eval()?.chars().count().to_string()), } } } -fn ast_from_rpn(rpn: &mut TokenStack) -> Result, String> { - match rpn.pop() { - None => Err("syntax error (premature end of expression)".to_owned()), - Some((token_idx, Token::Value { value })) => Ok(AstNode::new_leaf(token_idx, &value)), +struct Parser<'a> { + input: &'a [&'a str], + index: usize, +} + +impl<'a> Parser<'a> { + fn new(input: &'a [&'a str]) -> Self { + Self { input, index: 0 } + } - Some((token_idx, Token::InfixOp { value, .. })) => { - maybe_ast_node(token_idx, &value, 2, rpn) + fn next(&mut self) -> ExprResult<&'a str> { + let next = self.input.get(self.index); + if let Some(next) = next { + self.index += 1; + Ok(next) + } else { + // The indexing won't panic, because we know that the input size + // is greater than zero. + Err(ExprError::MissingArgument( + self.input[self.index - 1].into(), + )) } + } - Some((token_idx, Token::PrefixOp { value, arity })) => { - maybe_ast_node(token_idx, &value, arity, rpn) + fn accept(&mut self, f: impl Fn(&str) -> Option) -> Option { + let next = self.input.get(self.index)?; + let tok = f(next); + if let Some(tok) = tok { + self.index += 1; + Some(tok) + } else { + None } + } - Some((token_idx, unexpected_token)) => { - panic!("unexpected token at #{token_idx} {unexpected_token:?}") + fn parse(&mut self) -> ExprResult { + if self.input.is_empty() { + return Err(ExprError::MissingOperand); + } + let res = self.parse_expression()?; + if let Some(arg) = self.input.get(self.index) { + return Err(ExprError::UnexpectedArgument(arg.to_string())); } + Ok(res) } -} -fn maybe_ast_node( - token_idx: usize, - op_type: &str, - arity: usize, - rpn: &mut TokenStack, -) -> Result, String> { - let mut operands = Vec::with_capacity(arity); - for _ in 0..arity { - let operand = ast_from_rpn(rpn)?; - operands.push(operand); + fn parse_expression(&mut self) -> ExprResult { + self.parse_precedence(0) } - operands.reverse(); - Ok(AstNode::new_node(token_idx, op_type, operands)) -} -fn move_rest_of_ops_to_out( - out_stack: &mut TokenStack, - op_stack: &mut TokenStack, -) -> Result<(), String> { - loop { - match op_stack.pop() { - None => return Ok(()), - Some((token_idx, Token::ParOpen)) => { - return Err(format!( - "syntax error (Mismatched open-parenthesis at #{token_idx})" - )) - } - Some((token_idx, Token::ParClose)) => { - return Err(format!( - "syntax error (Mismatched close-parenthesis at #{token_idx})" - )) + fn parse_op(&mut self, precedence: usize) -> Option { + self.accept(|s| { + for (op_string, op) in PRECEDENCE[precedence] { + if s == *op_string { + return Some(*op); + } } - Some(other) => out_stack.push(other), - } + None + }) } -} -fn push_token_to_either_stack( - token_idx: usize, - token: &Token, - out_stack: &mut TokenStack, - op_stack: &mut TokenStack, -) -> Result<(), String> { - let result = match token { - Token::Value { .. } => { - out_stack.push((token_idx, token.clone())); - Ok(()) + fn parse_precedence(&mut self, precedence: usize) -> ExprResult { + if precedence >= PRECEDENCE.len() { + return self.parse_simple_expression(); } - Token::InfixOp { .. } => { - if op_stack.is_empty() { - op_stack.push((token_idx, token.clone())); - Ok(()) - } else { - push_op_to_stack(token_idx, token, out_stack, op_stack) - } + let mut left = self.parse_precedence(precedence + 1)?; + while let Some(op) = self.parse_op(precedence) { + let right = self.parse_precedence(precedence + 1)?; + left = AstNode::BinOp { + op_type: op, + left: Box::new(left), + right: Box::new(right), + }; } + Ok(left) + } - Token::ParOpen => { - if out_stack.is_empty() { - op_stack.push((token_idx, token.clone())); - Ok(()) - } else { - Err("syntax error: unexpected argument '('".to_string()) + fn parse_simple_expression(&mut self) -> ExprResult { + let first = self.next()?; + Ok(match first { + "match" => { + let left = self.parse_expression()?; + let right = self.parse_expression()?; + AstNode::BinOp { + op_type: BinOp::String(StringOp::Match), + left: Box::new(left), + right: Box::new(right), + } } - } - - Token::PrefixOp { value, .. } => { - if out_stack.is_empty() { - op_stack.push((token_idx, token.clone())); - Ok(()) - } else { - Err(format!( - "syntax error: unexpected argument {}", - value.quote() - )) + "substr" => { + let string = self.parse_expression()?; + let pos = self.parse_expression()?; + let length = self.parse_expression()?; + AstNode::Substr { + string: Box::new(string), + pos: Box::new(pos), + length: Box::new(length), + } } - } - - Token::ParClose => move_till_match_paren(out_stack, op_stack), - }; - maybe_dump_shunting_yard_step(token_idx, token, out_stack, op_stack, &result); - result -} - -#[allow(clippy::ptr_arg)] -fn maybe_dump_shunting_yard_step( - token_idx: usize, - token: &Token, - out_stack: &TokenStack, - op_stack: &TokenStack, - result: &Result<(), String>, -) { - use std::env; - if let Ok(debug_var) = env::var("EXPR_DEBUG_SYA_STEP") { - if debug_var == "1" { - println!("EXPR_DEBUG_SYA_STEP"); - println!("\t{token_idx} => {token:?}"); - println!("\t\tout: {out_stack:?}"); - println!("\t\top : {op_stack:?}"); - println!("\t\tresult: {result:?}"); - } - } -} - -fn push_op_to_stack( - token_idx: usize, - token: &Token, - out_stack: &mut TokenStack, - op_stack: &mut TokenStack, -) -> Result<(), String> { - if let Token::InfixOp { - precedence: prec, - left_assoc: la, - .. - } = *token - { - loop { - match op_stack.last() { - None | Some(&(_, Token::ParOpen)) => { - op_stack.push((token_idx, token.clone())); - return Ok(()); + "index" => { + let left = self.parse_expression()?; + let right = self.parse_expression()?; + AstNode::BinOp { + op_type: BinOp::String(StringOp::Index), + left: Box::new(left), + right: Box::new(right), } - - Some(&( - _, - Token::InfixOp { - precedence: prev_prec, - .. - }, - )) => { - if la && prev_prec >= prec || !la && prev_prec > prec { - out_stack.push(op_stack.pop().unwrap()); - } else { - op_stack.push((token_idx, token.clone())); - return Ok(()); - } + } + "length" => { + let string = self.parse_expression()?; + AstNode::Length { + string: Box::new(string), } - - Some(&(_, Token::PrefixOp { .. })) => { - op_stack.push((token_idx, token.clone())); - return Ok(()); + } + "+" => AstNode::Leaf { + value: self.next()?.into(), + }, + "(" => { + let s = self.parse_expression()?; + let close_paren = self.next()?; + if close_paren != ")" { + // Since we have parsed at least a '(', there will be a token + // at `self.index - 1`. So this indexing won't panic. + return Err(ExprError::ExpectedClosingBraceAfter( + self.input[self.index - 1].into(), + )); } - - Some(_) => panic!("Non-operator on op_stack"), + s } - } - } else { - panic!("Expected infix-op") + s => AstNode::Leaf { value: s.into() }, + }) } } -fn move_till_match_paren( - out_stack: &mut TokenStack, - op_stack: &mut TokenStack, -) -> Result<(), String> { - loop { - let op = op_stack - .pop() - .ok_or_else(|| "syntax error (Mismatched close-parenthesis)".to_string())?; - match op { - (_, Token::ParOpen) => return Ok(()), - other => out_stack.push(other), - } +/// Determine whether `expr` should evaluate the string as "truthy" +/// +/// Truthy strings are either empty or match the regex "-?0+". +pub fn is_truthy(s: &str) -> bool { + // Edge case: `-` followed by nothing is truthy + if s == "-" { + return true; } + + let mut bytes = s.bytes(); + + // Empty string is falsy + let Some(first) = bytes.next() else { + return false; + }; + + let is_zero = (first == b'-' || first == b'0') && bytes.all(|b| b == b'0'); + !is_zero } -fn infix_operator_two_ints(f: F, values: &[String]) -> Result -where - F: Fn(BigInt, BigInt) -> Result, -{ - assert!(values.len() == 2); - if let Ok(left) = values[0].parse::() { - if let Ok(right) = values[1].parse::() { - return f(left, right).map(|big_int| big_int.to_string()); +#[cfg(test)] +mod test { + use super::{AstNode, BinOp, NumericOp, RelationOp, StringOp}; + + impl From<&str> for AstNode { + fn from(value: &str) -> Self { + Self::Leaf { + value: value.into(), + } } } - Err("Expected an integer operand".to_string()) -} -fn infix_operator_two_ints_or_two_strings( - fi: FI, - fs: FS, - values: &[String], -) -> Result -where - FI: Fn(BigInt, BigInt) -> Result, - FS: Fn(&String, &String) -> Result, -{ - assert!(values.len() == 2); - if let (Some(a_int), Some(b_int)) = ( - values[0].parse::().ok(), - values[1].parse::().ok(), - ) { - match fi(a_int, b_int) { - Ok(result) => Ok(result.to_string()), - Err(reason) => Err(reason), + fn op(op_type: BinOp, left: impl Into, right: impl Into) -> AstNode { + AstNode::BinOp { + op_type, + left: Box::new(left.into()), + right: Box::new(right.into()), } - } else { - fs(&values[0], &values[1]) } -} -fn infix_operator_or(values: &[String]) -> String { - assert!(values.len() == 2); - if value_as_bool(&values[0]) { - values[0].clone() - } else if value_as_bool(&values[1]) { - values[1].clone() - } else { - 0.to_string() + fn length(string: impl Into) -> AstNode { + AstNode::Length { + string: Box::new(string.into()), + } } -} -fn infix_operator_and(values: &[String]) -> String { - assert!(values.len() == 2); - if value_as_bool(&values[0]) && value_as_bool(&values[1]) { - values[0].clone() - } else { - 0.to_string() + fn substr( + string: impl Into, + pos: impl Into, + length: impl Into, + ) -> AstNode { + AstNode::Substr { + string: Box::new(string.into()), + pos: Box::new(pos.into()), + length: Box::new(length.into()), + } } -} -fn operator_match(values: &[String]) -> Result { - assert!(values.len() == 2); - let re_string = format!("^{}", &values[1]); - let re = Regex::with_options(&re_string, RegexOptions::REGEX_OPTION_NONE, Syntax::grep()) - .map_err(|err| err.description().to_string())?; - Ok(if re.captures_len() > 0 { - re.captures(&values[0]) - .map(|captures| captures.at(1).unwrap()) - .unwrap_or("") - .to_string() - } else { - re.find(&values[0]) - .map_or("0".to_string(), |(start, end)| (end - start).to_string()) - }) -} - -fn prefix_operator_length(values: &[String]) -> String { - assert!(values.len() == 1); - // Use chars().count() as we can have some multibytes chars - // See https://github.com/uutils/coreutils/issues/3132 - values[0].chars().count().to_string() -} - -fn prefix_operator_index(values: &[String]) -> String { - assert!(values.len() == 2); - let haystack = &values[0]; - let needles = &values[1]; - - for (current_idx, ch_h) in haystack.chars().enumerate() { - for ch_n in needles.chars() { - if ch_n == ch_h { - return (current_idx + 1).to_string(); - } + #[test] + fn infix_operators() { + let cases = [ + ("|", BinOp::String(StringOp::Or)), + ("&", BinOp::String(StringOp::And)), + ("<", BinOp::Relation(RelationOp::Lt)), + ("<=", BinOp::Relation(RelationOp::Leq)), + ("=", BinOp::Relation(RelationOp::Eq)), + ("!=", BinOp::Relation(RelationOp::Neq)), + (">=", BinOp::Relation(RelationOp::Geq)), + (">", BinOp::Relation(RelationOp::Gt)), + ("+", BinOp::Numeric(NumericOp::Add)), + ("-", BinOp::Numeric(NumericOp::Sub)), + ("*", BinOp::Numeric(NumericOp::Mul)), + ("/", BinOp::Numeric(NumericOp::Div)), + ("%", BinOp::Numeric(NumericOp::Mod)), + (":", BinOp::String(StringOp::Match)), + ]; + for (string, value) in cases { + assert_eq!(AstNode::parse(&["1", string, "2"]), Ok(op(value, "1", "2"))); } } - "0".to_string() -} - -fn prefix_operator_substr(values: &[String]) -> String { - assert!(values.len() == 3); - let subj = &values[0]; - let idx = match values[1] - .parse::() - .ok() - .and_then(|v| v.checked_sub(1)) - { - Some(i) => i, - None => return String::new(), - }; - let len = match values[2].parse::() { - Ok(i) => i, - Err(_) => return String::new(), - }; - - subj.chars().skip(idx).take(len).collect() -} - -fn bool_as_int(b: bool) -> u8 { - u8::from(b) -} -fn bool_as_string(b: bool) -> String { - if b { - "1".to_string() - } else { - "0".to_string() + #[test] + fn other_operators() { + assert_eq!( + AstNode::parse(&["match", "1", "2"]), + Ok(op(BinOp::String(StringOp::Match), "1", "2")), + ); + assert_eq!( + AstNode::parse(&["index", "1", "2"]), + Ok(op(BinOp::String(StringOp::Index), "1", "2")), + ); + assert_eq!(AstNode::parse(&["length", "1"]), Ok(length("1")),); + assert_eq!( + AstNode::parse(&["substr", "1", "2", "3"]), + Ok(substr("1", "2", "3")), + ); } -} -fn value_as_bool(s: &str) -> bool { - if s.is_empty() { - return false; - } - match s.parse::() { - Ok(n) => n != Zero::zero(), - Err(_) => true, + #[test] + fn precedence() { + assert_eq!( + AstNode::parse(&["1", "+", "2", "*", "3"]), + Ok(op( + BinOp::Numeric(NumericOp::Add), + "1", + op(BinOp::Numeric(NumericOp::Mul), "2", "3") + )) + ); + assert_eq!( + AstNode::parse(&["(", "1", "+", "2", ")", "*", "3"]), + Ok(op( + BinOp::Numeric(NumericOp::Mul), + op(BinOp::Numeric(NumericOp::Add), "1", "2"), + "3" + )) + ); + assert_eq!( + AstNode::parse(&["1", "*", "2", "+", "3"]), + Ok(op( + BinOp::Numeric(NumericOp::Add), + op(BinOp::Numeric(NumericOp::Mul), "1", "2"), + "3" + )), + ); } } diff --git a/src/uu/expr/src/tokens.rs b/src/uu/expr/src/tokens.rs deleted file mode 100644 index f499881c138..00000000000 --- a/src/uu/expr/src/tokens.rs +++ /dev/null @@ -1,147 +0,0 @@ -// This file is part of the uutils coreutils package. -// -// For the full copyright and license information, please view the LICENSE -// file that was distributed with this source code. - -//! -//! The following tokens are present in the expr grammar: -//! * integer literal; -//! * string literal; -//! * infix binary operators; -//! * prefix operators. -//! -//! According to the man-page of expr we have expression split into tokens (each token -- separate CLI-argument). -//! Hence all we need is to map the strings into the Token structures, except for some ugly fiddling with +-escaping. -//! - -// spell-checker:ignore (ToDO) paren - -#[derive(Debug, Clone)] -pub enum Token { - Value { - value: String, - }, - - ParOpen, - ParClose, - - InfixOp { - precedence: u8, - left_assoc: bool, - value: String, - }, - - PrefixOp { - arity: usize, - value: String, - }, -} - -impl Token { - fn new_infix_op(v: &str, left_assoc: bool, precedence: u8) -> Self { - Self::InfixOp { - left_assoc, - precedence, - value: v.into(), - } - } - - fn new_value(v: &str) -> Self { - Self::Value { value: v.into() } - } - - fn is_infix_plus(&self) -> bool { - match self { - Self::InfixOp { value, .. } => value == "+", - _ => false, - } - } - - fn is_a_value(&self) -> bool { - matches!(*self, Self::Value { .. }) - } - - fn is_a_close_paren(&self) -> bool { - matches!(*self, Self::ParClose) - } -} - -pub fn strings_to_tokens(strings: &[&str]) -> Result, String> { - let mut tokens_acc = Vec::with_capacity(strings.len()); - let mut tok_idx = 1; - - for s in strings { - let token_if_not_escaped = match *s { - "(" => Token::ParOpen, - ")" => Token::ParClose, - - "^" => Token::new_infix_op(s, false, 7), - - ":" => Token::new_infix_op(s, true, 6), - - "*" | "/" | "%" => Token::new_infix_op(s, true, 5), - - "+" | "-" => Token::new_infix_op(s, true, 4), - - "=" | "!=" | "<" | ">" | "<=" | ">=" => Token::new_infix_op(s, true, 3), - - "&" => Token::new_infix_op(s, true, 2), - - "|" => Token::new_infix_op(s, true, 1), - - "match" | "index" => Token::PrefixOp { - arity: 2, - value: s.to_string(), - }, - "substr" => Token::PrefixOp { - arity: 3, - value: s.to_string(), - }, - "length" => Token::PrefixOp { - arity: 1, - value: s.to_string(), - }, - - _ => Token::new_value(s), - }; - push_token_if_not_escaped(&mut tokens_acc, tok_idx, token_if_not_escaped, s); - tok_idx += 1; - } - maybe_dump_tokens_acc(&tokens_acc); - - Ok(tokens_acc) -} - -fn maybe_dump_tokens_acc(tokens_acc: &[(usize, Token)]) { - use std::env; - - if let Ok(debug_var) = env::var("EXPR_DEBUG_TOKENS") { - if debug_var == "1" { - println!("EXPR_DEBUG_TOKENS"); - for token in tokens_acc { - println!("\t{token:?}"); - } - } - } -} - -fn push_token_if_not_escaped(acc: &mut Vec<(usize, Token)>, tok_idx: usize, token: Token, s: &str) { - // `+` may be escaped such as `expr + 1` and `expr 1 + + 1` - let prev_is_plus = match acc.last() { - None => false, - Some(t) => t.1.is_infix_plus(), - }; - let should_use_as_escaped = if prev_is_plus && acc.len() >= 2 { - let pre_prev = &acc[acc.len() - 2]; - !(pre_prev.1.is_a_value() || pre_prev.1.is_a_close_paren()) - } else { - prev_is_plus - }; - - if should_use_as_escaped { - acc.pop(); - acc.push((tok_idx, Token::new_value(s))); - } else { - acc.push((tok_idx, token)); - } -} From 01c32a5220ef036bdc1d9bae8928336a815db619 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Tue, 28 Nov 2023 11:40:33 +0100 Subject: [PATCH 0411/2851] fmt: clean up some small bits --- src/uu/fmt/src/linebreak.rs | 37 +++--- src/uu/fmt/src/parasplit.rs | 232 ++++++++++++++++++------------------ 2 files changed, 132 insertions(+), 137 deletions(-) diff --git a/src/uu/fmt/src/linebreak.rs b/src/uu/fmt/src/linebreak.rs index fbd990fff1e..7cd65d86149 100644 --- a/src/uu/fmt/src/linebreak.rs +++ b/src/uu/fmt/src/linebreak.rs @@ -46,7 +46,7 @@ pub fn break_lines( ostream: &mut BufWriter, ) -> std::io::Result<()> { // indent - let p_indent = ¶.indent_str[..]; + let p_indent = ¶.indent_str; let p_indent_len = para.indent_len; // words @@ -55,14 +55,12 @@ pub fn break_lines( // the first word will *always* appear on the first line // make sure of this here - let (w, w_len) = match p_words_words.next() { - Some(winfo) => (winfo.word, winfo.word_nchars), - None => { - return ostream.write_all(b"\n"); - } + let Some(winfo) = p_words_words.next() else { + return ostream.write_all(b"\n"); }; + // print the init, if it exists, and get its length - let p_init_len = w_len + let p_init_len = winfo.word_nchars + if opts.crown || opts.tagged { // handle "init" portion ostream.write_all(para.init_str.as_bytes())?; @@ -75,8 +73,9 @@ pub fn break_lines( // except that mail headers get no indent at all 0 }; + // write first word after writing init - ostream.write_all(w.as_bytes())?; + ostream.write_all(winfo.word.as_bytes())?; // does this paragraph require uniform spacing? let uniform = para.mail_header || opts.uniform; @@ -103,15 +102,16 @@ fn break_simple<'a, T: Iterator>>( mut iter: T, args: &mut BreakArgs<'a>, ) -> std::io::Result<()> { - iter.try_fold((args.init_len, false), |l, winfo| { - accum_words_simple(args, l, winfo) + iter.try_fold((args.init_len, false), |(l, prev_punct), winfo| { + accum_words_simple(args, l, prev_punct, winfo) })?; args.ostream.write_all(b"\n") } fn accum_words_simple<'a>( args: &mut BreakArgs<'a>, - (l, prev_punct): (usize, bool), + l: usize, + prev_punct: bool, winfo: &'a WordInfo<'a>, ) -> std::io::Result<(usize, bool)> { // compute the length of this word, considering how tabs will expand at this position on the line @@ -233,14 +233,14 @@ fn find_kp_breakpoints<'a, T: Iterator>>( linebreak: None, break_before: false, demerits: 0, - prev_rat: 0.0f32, + prev_rat: 0.0, length: args.init_len, fresh: false, }]; // this vec holds the current active linebreaks; next_ holds the breaks that will be active for // the next word - let active_breaks = &mut vec![0]; - let next_active_breaks = &mut vec![]; + let mut active_breaks = vec![0]; + let mut next_active_breaks = vec![]; let stretch = (args.opts.width - args.opts.goal) as isize; let minlength = args.opts.goal - stretch as usize; @@ -248,10 +248,7 @@ fn find_kp_breakpoints<'a, T: Iterator>>( let mut is_sentence_start = false; let mut least_demerits = 0; loop { - let w = match iter.next() { - None => break, - Some(w) => w, - }; + let Some(w) = iter.next() else { break }; // if this is the last word, we don't add additional demerits for this break let (is_last_word, is_sentence_end) = match iter.peek() { @@ -358,13 +355,13 @@ fn find_kp_breakpoints<'a, T: Iterator>>( least_demerits = cmp::max(ld_next, 0); } // swap in new list of active breaks - mem::swap(active_breaks, next_active_breaks); + mem::swap(&mut active_breaks, &mut next_active_breaks); // If this was the last word in a sentence, the next one must be the first in the next. is_sentence_start = is_sentence_end; } // return the best path - build_best_path(&linebreaks, active_breaks) + build_best_path(&linebreaks, &active_breaks) } fn build_best_path<'a>(paths: &[LineBreak<'a>], active: &[usize]) -> Vec<(&'a WordInfo<'a>, bool)> { diff --git a/src/uu/fmt/src/parasplit.rs b/src/uu/fmt/src/parasplit.rs index 68c8f78fa89..311ddbc9b83 100644 --- a/src/uu/fmt/src/parasplit.rs +++ b/src/uu/fmt/src/parasplit.rs @@ -52,18 +52,22 @@ impl Line { } } -// each line's prefix has to be considered to know whether to merge it with -// the next line or not +/// Each line's prefix has to be considered to know whether to merge it with +/// the next line or not #[derive(Debug)] pub struct FileLine { line: String, - indent_end: usize, // the end of the indent, always the start of the text - pfxind_end: usize, // the end of the PREFIX's indent, that is, the spaces before the prefix - indent_len: usize, // display length of indent taking into account tabs - prefix_len: usize, // PREFIX indent length taking into account tabs + /// The end of the indent, always the start of the text + indent_end: usize, + /// The end of the PREFIX's indent, that is, the spaces before the prefix + pfxind_end: usize, + /// Display length of indent taking into account tabs + indent_len: usize, + /// PREFIX indent length taking into account tabs + prefix_len: usize, } -// iterator that produces a stream of Lines from a file +/// Iterator that produces a stream of Lines from a file pub struct FileLines<'a> { opts: &'a FmtOptions, lines: Lines<&'a mut FileOrStdReader>, @@ -74,7 +78,7 @@ impl<'a> FileLines<'a> { FileLines { opts, lines } } - // returns true if this line should be formatted + /// returns true if this line should be formatted fn match_prefix(&self, line: &str) -> (bool, usize) { if !self.opts.use_prefix { return (true, 0); @@ -83,7 +87,7 @@ impl<'a> FileLines<'a> { FileLines::match_prefix_generic(&self.opts.prefix[..], line, self.opts.xprefix) } - // returns true if this line should be formatted + /// returns true if this line should be formatted fn match_anti_prefix(&self, line: &str) -> bool { if !self.opts.use_anti_prefix { return true; @@ -148,13 +152,7 @@ impl<'a> Iterator for FileLines<'a> { type Item = Line; fn next(&mut self) -> Option { - let n = match self.lines.next() { - Some(t) => match t { - Ok(tt) => tt, - Err(_) => return None, - }, - None => return None, - }; + let n = self.lines.next()?.ok()?; // if this line is entirely whitespace, // emit a blank line @@ -205,24 +203,33 @@ impl<'a> Iterator for FileLines<'a> { } } -// a paragraph : a collection of FileLines that are to be formatted -// plus info about the paragraph's indentation -// (but we only retain the String from the FileLine; the other info -// is only there to help us in deciding how to merge lines into Paragraphs +/// A paragraph : a collection of FileLines that are to be formatted +/// plus info about the paragraph's indentation +/// +/// We only retain the String from the FileLine; the other info +/// is only there to help us in deciding how to merge lines into Paragraphs #[derive(Debug)] pub struct Paragraph { - lines: Vec, // the lines of the file - pub init_str: String, // string representing the init, that is, the first line's indent - pub init_len: usize, // printable length of the init string considering TABWIDTH - init_end: usize, // byte location of end of init in first line String - pub indent_str: String, // string representing indent - pub indent_len: usize, // length of above - indent_end: usize, // byte location of end of indent (in crown and tagged mode, only applies to 2nd line and onward) - pub mail_header: bool, // we need to know if this is a mail header because we do word splitting differently in that case + /// the lines of the file + lines: Vec, + /// string representing the init, that is, the first line's indent + pub init_str: String, + /// printable length of the init string considering TABWIDTH + pub init_len: usize, + /// byte location of end of init in first line String + init_end: usize, + /// string representing indent + pub indent_str: String, + /// length of above + pub indent_len: usize, + /// byte location of end of indent (in crown and tagged mode, only applies to 2nd line and onward) + indent_end: usize, + /// we need to know if this is a mail header because we do word splitting differently in that case + pub mail_header: bool, } -// an iterator producing a stream of paragraphs from a stream of lines -// given a set of options. +/// An iterator producing a stream of paragraphs from a stream of lines +/// given a set of options. pub struct ParagraphStream<'a> { lines: Peekable>, next_mail: bool, @@ -240,7 +247,7 @@ impl<'a> ParagraphStream<'a> { } } - // detect RFC822 mail header + /// Detect RFC822 mail header fn is_mail_header(line: &FileLine) -> bool { // a mail header begins with either "From " (envelope sender line) // or with a sequence of printable ASCII chars (33 to 126, inclusive, @@ -276,12 +283,9 @@ impl<'a> Iterator for ParagraphStream<'a> { #[allow(clippy::cognitive_complexity)] fn next(&mut self) -> Option> { // return a NoFormatLine in an Err; it should immediately be output - let noformat = match self.lines.peek() { - None => return None, - Some(l) => match *l { - Line::FormatLine(_) => false, - Line::NoFormatLine(_, _) => true, - }, + let noformat = match self.lines.peek()? { + Line::FormatLine(_) => false, + Line::NoFormatLine(_, _) => true, }; // found a NoFormatLine, immediately dump it out @@ -305,95 +309,89 @@ impl<'a> Iterator for ParagraphStream<'a> { let mut in_mail = false; let mut second_done = false; // for when we use crown or tagged mode loop { - { - // peek ahead - // need to explicitly force fl out of scope before we can call self.lines.next() - let fl = match self.lines.peek() { - None => break, - Some(l) => match *l { - Line::FormatLine(ref x) => x, - Line::NoFormatLine(..) => break, - }, - }; + // peek ahead + // need to explicitly force fl out of scope before we can call self.lines.next() + let Some(Line::FormatLine(fl)) = self.lines.peek() else { + break; + }; - if p_lines.is_empty() { - // first time through the loop, get things set up - // detect mail header - if self.opts.mail && self.next_mail && ParagraphStream::is_mail_header(fl) { - in_mail = true; - // there can't be any indent or pfxind because otherwise is_mail_header - // would fail since there cannot be any whitespace before the colon in a - // valid header field - indent_str.push_str(" "); - indent_len = 2; + if p_lines.is_empty() { + // first time through the loop, get things set up + // detect mail header + if self.opts.mail && self.next_mail && ParagraphStream::is_mail_header(fl) { + in_mail = true; + // there can't be any indent or pfxind because otherwise is_mail_header + // would fail since there cannot be any whitespace before the colon in a + // valid header field + indent_str.push_str(" "); + indent_len = 2; + } else { + if self.opts.crown || self.opts.tagged { + init_str.push_str(&fl.line[..fl.indent_end]); + init_len = fl.indent_len; + init_end = fl.indent_end; } else { - if self.opts.crown || self.opts.tagged { - init_str.push_str(&fl.line[..fl.indent_end]); - init_len = fl.indent_len; - init_end = fl.indent_end; - } else { - second_done = true; - } - - // these will be overwritten in the 2nd line of crown or tagged mode, but - // we are not guaranteed to get to the 2nd line, e.g., if the next line - // is a NoFormatLine or None. Thus, we set sane defaults the 1st time around - indent_str.push_str(&fl.line[..fl.indent_end]); - indent_len = fl.indent_len; - indent_end = fl.indent_end; - - // save these to check for matching lines - prefix_len = fl.prefix_len; - pfxind_end = fl.pfxind_end; - - // in tagged mode, add 4 spaces of additional indenting by default - // (gnu fmt's behavior is different: it seems to find the closest column to - // indent_end that is divisible by 3. But honestly that behavior seems - // pretty arbitrary. - // Perhaps a better default would be 1 TABWIDTH? But ugh that's so big. - if self.opts.tagged { - indent_str.push_str(" "); - indent_len += 4; - } - } - } else if in_mail { - // lines following mail headers must begin with spaces - if fl.indent_end == 0 || (self.opts.use_prefix && fl.pfxind_end == 0) { - break; // this line does not begin with spaces + second_done = true; } - } else if !second_done { - // now we have enough info to handle crown margin and tagged mode - // in both crown and tagged modes we require that prefix_len is the same - if prefix_len != fl.prefix_len || pfxind_end != fl.pfxind_end { - break; - } - - // in tagged mode, indent has to be *different* on following lines - if self.opts.tagged - && indent_len - 4 == fl.indent_len - && indent_end == fl.indent_end - { - break; - } - - // this is part of the same paragraph, get the indent info from this line - indent_str.clear(); + // these will be overwritten in the 2nd line of crown or tagged mode, but + // we are not guaranteed to get to the 2nd line, e.g., if the next line + // is a NoFormatLine or None. Thus, we set sane defaults the 1st time around indent_str.push_str(&fl.line[..fl.indent_end]); indent_len = fl.indent_len; indent_end = fl.indent_end; - second_done = true; - } else { - // detect mismatch - if indent_end != fl.indent_end - || pfxind_end != fl.pfxind_end - || indent_len != fl.indent_len - || prefix_len != fl.prefix_len - { - break; + // save these to check for matching lines + prefix_len = fl.prefix_len; + pfxind_end = fl.pfxind_end; + + // in tagged mode, add 4 spaces of additional indenting by default + // (gnu fmt's behavior is different: it seems to find the closest column to + // indent_end that is divisible by 3. But honestly that behavior seems + // pretty arbitrary. + // Perhaps a better default would be 1 TABWIDTH? But ugh that's so big. + if self.opts.tagged { + indent_str.push_str(" "); + indent_len += 4; } } + } else if in_mail { + // lines following mail headers must begin with spaces + if fl.indent_end == 0 || (self.opts.use_prefix && fl.pfxind_end == 0) { + break; // this line does not begin with spaces + } + } else if !second_done { + // now we have enough info to handle crown margin and tagged mode + + // in both crown and tagged modes we require that prefix_len is the same + if prefix_len != fl.prefix_len || pfxind_end != fl.pfxind_end { + break; + } + + // in tagged mode, indent has to be *different* on following lines + if self.opts.tagged + && indent_len - 4 == fl.indent_len + && indent_end == fl.indent_end + { + break; + } + + // this is part of the same paragraph, get the indent info from this line + indent_str.clear(); + indent_str.push_str(&fl.line[..fl.indent_end]); + indent_len = fl.indent_len; + indent_end = fl.indent_end; + + second_done = true; + } else { + // detect mismatch + if indent_end != fl.indent_end + || pfxind_end != fl.pfxind_end + || indent_len != fl.indent_len + || prefix_len != fl.prefix_len + { + break; + } } p_lines.push(self.lines.next().unwrap().get_formatline().line); @@ -429,7 +427,7 @@ pub struct ParaWords<'a> { } impl<'a> ParaWords<'a> { - pub fn new<'b>(opts: &'b FmtOptions, para: &'b Paragraph) -> ParaWords<'b> { + pub fn new(opts: &'a FmtOptions, para: &'a Paragraph) -> Self { let mut pw = ParaWords { opts, para, From d78923e4ccda95db136c358913a09642f4ee9729 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Tue, 28 Nov 2023 11:54:43 +0100 Subject: [PATCH 0412/2851] fmt: extract determining options to separate function --- src/uu/fmt/src/fmt.rs | 194 +++++++++++++++++++++--------------------- 1 file changed, 95 insertions(+), 99 deletions(-) diff --git a/src/uu/fmt/src/fmt.rs b/src/uu/fmt/src/fmt.rs index c30d923b76b..3a494c868e5 100644 --- a/src/uu/fmt/src/fmt.rs +++ b/src/uu/fmt/src/fmt.rs @@ -5,7 +5,7 @@ // spell-checker:ignore (ToDO) PSKIP linebreak ostream parasplit tabwidth xanti xprefix -use clap::{crate_version, Arg, ArgAction, Command}; +use clap::{crate_version, Arg, ArgAction, ArgMatches, Command}; use std::cmp; use std::fs::File; use std::io::{stdin, stdout, Write}; @@ -40,6 +40,9 @@ static OPT_TAB_WIDTH: &str = "tab-width"; static ARG_FILES: &str = "files"; +// by default, goal is 93% of width +const DEFAULT_GOAL_TO_WIDTH_RATIO: usize = 93; + pub type FileOrStdReader = BufReader>; pub struct FmtOptions { crown: bool, @@ -59,25 +62,97 @@ pub struct FmtOptions { tabwidth: usize, } -impl Default for FmtOptions { - fn default() -> Self { - Self { - crown: false, - tagged: false, - mail: false, - uniform: false, - quick: false, - split_only: false, - use_prefix: false, - prefix: String::new(), - xprefix: false, - use_anti_prefix: false, - anti_prefix: String::new(), - xanti_prefix: false, - width: 75, - goal: 70, - tabwidth: 8, +impl FmtOptions { + fn from_matches(matches: &ArgMatches) -> UResult { + let mut tagged = matches.get_flag(OPT_TAGGED_PARAGRAPH); + let mut crown = matches.get_flag(OPT_CROWN_MARGIN); + + let mail = matches.get_flag(OPT_PRESERVE_HEADERS); + let uniform = matches.get_flag(OPT_UNIFORM_SPACING); + let quick = matches.get_flag(OPT_QUICK); + let split_only = matches.get_flag(OPT_SPLIT_ONLY); + + if crown { + tagged = false; + } + if split_only { + crown = false; + tagged = false; + } + + let xprefix = matches.contains_id(OPT_EXACT_PREFIX); + let xanti_prefix = matches.contains_id(OPT_SKIP_PREFIX); + + let mut prefix = String::new(); + let mut use_prefix = false; + if let Some(s) = matches.get_one::(OPT_PREFIX).map(String::from) { + prefix = s; + use_prefix = true; + }; + + let mut anti_prefix = String::new(); + let mut use_anti_prefix = false; + if let Some(s) = matches.get_one::(OPT_SKIP_PREFIX).map(String::from) { + anti_prefix = s; + use_anti_prefix = true; + }; + + let mut width = 75; + let mut goal = 70; + if let Some(w) = matches.get_one::(OPT_WIDTH) { + width = *w; + if width > MAX_WIDTH { + return Err(USimpleError::new( + 1, + format!("invalid width: '{}': Numerical result out of range", width), + )); + } + goal = cmp::min(width * DEFAULT_GOAL_TO_WIDTH_RATIO / 100, width - 3); + }; + + if let Some(g) = matches.get_one::(OPT_GOAL) { + goal = *g; + if !matches.contains_id(OPT_WIDTH) { + width = cmp::max(goal * 100 / DEFAULT_GOAL_TO_WIDTH_RATIO, goal + 3); + } else if goal > width { + return Err(USimpleError::new(1, "GOAL cannot be greater than WIDTH.")); + } + }; + + let mut tabwidth = 8; + if let Some(s) = matches.get_one::(OPT_TAB_WIDTH) { + tabwidth = match s.parse::() { + Ok(t) => t, + Err(e) => { + return Err(USimpleError::new( + 1, + format!("Invalid TABWIDTH specification: {}: {}", s.quote(), e), + )); + } + }; + }; + + if tabwidth < 1 { + tabwidth = 1; } + + Ok(Self { + crown, + tagged, + mail, + uniform, + quick, + split_only, + use_prefix, + prefix, + xprefix, + use_anti_prefix, + anti_prefix, + xanti_prefix, + width, + goal, + tabwidth, + }) } } @@ -90,12 +165,7 @@ impl Default for FmtOptions { /// # Returns /// /// A tuple containing a vector of file names and a `FmtOptions` struct. -#[allow(clippy::cognitive_complexity)] -#[allow(clippy::field_reassign_with_default)] fn parse_arguments(args: impl uucore::Args) -> UResult<(Vec, FmtOptions)> { - // by default, goal is 93% of width - const DEFAULT_GOAL_TO_WIDTH_RATIO: usize = 93; - let matches = uu_app().try_get_matches_from(args)?; let mut files: Vec = matches @@ -103,81 +173,7 @@ fn parse_arguments(args: impl uucore::Args) -> UResult<(Vec, FmtOptions) .map(|v| v.map(ToString::to_string).collect()) .unwrap_or_default(); - let mut fmt_opts = FmtOptions::default(); - - fmt_opts.tagged = matches.get_flag(OPT_TAGGED_PARAGRAPH); - if matches.get_flag(OPT_CROWN_MARGIN) { - fmt_opts.crown = true; - fmt_opts.tagged = false; - } - fmt_opts.mail = matches.get_flag(OPT_PRESERVE_HEADERS); - fmt_opts.uniform = matches.get_flag(OPT_UNIFORM_SPACING); - fmt_opts.quick = matches.get_flag(OPT_QUICK); - if matches.get_flag(OPT_SPLIT_ONLY) { - fmt_opts.split_only = true; - fmt_opts.crown = false; - fmt_opts.tagged = false; - } - fmt_opts.xprefix = matches.contains_id(OPT_EXACT_PREFIX); - fmt_opts.xanti_prefix = matches.contains_id(OPT_SKIP_PREFIX); - - if let Some(s) = matches.get_one::(OPT_PREFIX).map(String::from) { - fmt_opts.prefix = s; - fmt_opts.use_prefix = true; - }; - - if let Some(s) = matches.get_one::(OPT_SKIP_PREFIX).map(String::from) { - fmt_opts.anti_prefix = s; - fmt_opts.use_anti_prefix = true; - }; - - if let Some(width) = matches.get_one::(OPT_WIDTH) { - fmt_opts.width = *width; - if fmt_opts.width > MAX_WIDTH { - return Err(USimpleError::new( - 1, - format!( - "invalid width: '{}': Numerical result out of range", - fmt_opts.width, - ), - )); - } - fmt_opts.goal = cmp::min( - fmt_opts.width * DEFAULT_GOAL_TO_WIDTH_RATIO / 100, - fmt_opts.width - 3, - ); - }; - - if let Some(goal) = matches.get_one::(OPT_GOAL) { - fmt_opts.goal = *goal; - if !matches.contains_id(OPT_WIDTH) { - fmt_opts.width = cmp::max( - fmt_opts.goal * 100 / DEFAULT_GOAL_TO_WIDTH_RATIO, - fmt_opts.goal + 3, - ); - } else if fmt_opts.goal > fmt_opts.width { - return Err(USimpleError::new(1, "GOAL cannot be greater than WIDTH.")); - } - }; - - if let Some(s) = matches.get_one::(OPT_TAB_WIDTH) { - fmt_opts.tabwidth = match s.parse::() { - Ok(t) => t, - Err(e) => { - return Err(USimpleError::new( - 1, - format!("Invalid TABWIDTH specification: {}: {}", s.quote(), e), - )); - } - }; - }; - - if fmt_opts.tabwidth < 1 { - fmt_opts.tabwidth = 1; - } - - // immutable now - let fmt_opts = fmt_opts; + let fmt_opts = FmtOptions::from_matches(&matches)?; if files.is_empty() { files.push("-".to_owned()); From f5206ce783d1606432c20f67d8ab027fcab06e7c Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Tue, 28 Nov 2023 12:05:35 +0100 Subject: [PATCH 0413/2851] fmt: merge prefix and use_prefix options (same for anti_prefix) --- src/uu/fmt/src/fmt.rs | 23 ++++------------------- src/uu/fmt/src/parasplit.rs | 22 +++++++++------------- 2 files changed, 13 insertions(+), 32 deletions(-) diff --git a/src/uu/fmt/src/fmt.rs b/src/uu/fmt/src/fmt.rs index 3a494c868e5..3a02c642902 100644 --- a/src/uu/fmt/src/fmt.rs +++ b/src/uu/fmt/src/fmt.rs @@ -49,11 +49,9 @@ pub struct FmtOptions { tagged: bool, mail: bool, split_only: bool, - use_prefix: bool, - prefix: String, + prefix: Option, xprefix: bool, - use_anti_prefix: bool, - anti_prefix: String, + anti_prefix: Option, xanti_prefix: bool, uniform: bool, quick: bool, @@ -83,19 +81,8 @@ impl FmtOptions { let xprefix = matches.contains_id(OPT_EXACT_PREFIX); let xanti_prefix = matches.contains_id(OPT_SKIP_PREFIX); - let mut prefix = String::new(); - let mut use_prefix = false; - if let Some(s) = matches.get_one::(OPT_PREFIX).map(String::from) { - prefix = s; - use_prefix = true; - }; - - let mut anti_prefix = String::new(); - let mut use_anti_prefix = false; - if let Some(s) = matches.get_one::(OPT_SKIP_PREFIX).map(String::from) { - anti_prefix = s; - use_anti_prefix = true; - }; + let prefix = matches.get_one::(OPT_PREFIX).map(String::from); + let anti_prefix = matches.get_one::(OPT_SKIP_PREFIX).map(String::from); let mut width = 75; let mut goal = 70; @@ -143,10 +130,8 @@ impl FmtOptions { uniform, quick, split_only, - use_prefix, prefix, xprefix, - use_anti_prefix, anti_prefix, xanti_prefix, width, diff --git a/src/uu/fmt/src/parasplit.rs b/src/uu/fmt/src/parasplit.rs index 311ddbc9b83..f22400dff20 100644 --- a/src/uu/fmt/src/parasplit.rs +++ b/src/uu/fmt/src/parasplit.rs @@ -80,24 +80,20 @@ impl<'a> FileLines<'a> { /// returns true if this line should be formatted fn match_prefix(&self, line: &str) -> (bool, usize) { - if !self.opts.use_prefix { + let Some(prefix) = &self.opts.prefix else { return (true, 0); - } + }; - FileLines::match_prefix_generic(&self.opts.prefix[..], line, self.opts.xprefix) + FileLines::match_prefix_generic(prefix, line, self.opts.xprefix) } /// returns true if this line should be formatted fn match_anti_prefix(&self, line: &str) -> bool { - if !self.opts.use_anti_prefix { + let Some(anti_prefix) = &self.opts.anti_prefix else { return true; - } + }; - match FileLines::match_prefix_generic( - &self.opts.anti_prefix[..], - line, - self.opts.xanti_prefix, - ) { + match FileLines::match_prefix_generic(anti_prefix, line, self.opts.xanti_prefix) { (true, _) => false, (_, _) => true, } @@ -176,7 +172,7 @@ impl<'a> Iterator for FileLines<'a> { // not truly blank we will not allow mail headers on the // following line) if pmatch - && n[poffset + self.opts.prefix.len()..] + && n[poffset + self.opts.prefix.as_ref().map_or(0, |s| s.len())..] .chars() .all(char::is_whitespace) { @@ -190,7 +186,7 @@ impl<'a> Iterator for FileLines<'a> { } // figure out the indent, prefix, and prefixindent ending points - let prefix_end = poffset + self.opts.prefix.len(); + let prefix_end = poffset + self.opts.prefix.as_ref().map_or(0, |s| s.len()); let (indent_end, prefix_len, indent_len) = self.compute_indent(&n[..], prefix_end); Some(Line::FormatLine(FileLine { @@ -357,7 +353,7 @@ impl<'a> Iterator for ParagraphStream<'a> { } } else if in_mail { // lines following mail headers must begin with spaces - if fl.indent_end == 0 || (self.opts.use_prefix && fl.pfxind_end == 0) { + if fl.indent_end == 0 || (self.opts.prefix.is_some() && fl.pfxind_end == 0) { break; // this line does not begin with spaces } } else if !second_done { From 96ca5e609eacf4fd09316da2c4bbd165ff052273 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Tue, 28 Nov 2023 12:22:46 +0100 Subject: [PATCH 0414/2851] fmt: refactor width and goal calculation --- src/uu/fmt/src/fmt.rs | 42 +++++++++++++++++++++++------------------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/src/uu/fmt/src/fmt.rs b/src/uu/fmt/src/fmt.rs index 3a02c642902..0ed32641fbb 100644 --- a/src/uu/fmt/src/fmt.rs +++ b/src/uu/fmt/src/fmt.rs @@ -6,7 +6,6 @@ // spell-checker:ignore (ToDO) PSKIP linebreak ostream parasplit tabwidth xanti xprefix use clap::{crate_version, Arg, ArgAction, ArgMatches, Command}; -use std::cmp; use std::fs::File; use std::io::{stdin, stdout, Write}; use std::io::{BufReader, BufWriter, Read, Stdout}; @@ -84,28 +83,33 @@ impl FmtOptions { let prefix = matches.get_one::(OPT_PREFIX).map(String::from); let anti_prefix = matches.get_one::(OPT_SKIP_PREFIX).map(String::from); - let mut width = 75; - let mut goal = 70; - if let Some(w) = matches.get_one::(OPT_WIDTH) { - width = *w; - if width > MAX_WIDTH { - return Err(USimpleError::new( - 1, - format!("invalid width: '{}': Numerical result out of range", width), - )); + let width_opt = matches.get_one::(OPT_WIDTH); + let goal_opt = matches.get_one::(OPT_GOAL); + let (width, goal) = match (width_opt, goal_opt) { + (Some(&w), Some(&g)) => { + if g > w { + return Err(USimpleError::new(1, "GOAL cannot be greater than WIDTH.")); + } + (w, g) } - goal = cmp::min(width * DEFAULT_GOAL_TO_WIDTH_RATIO / 100, width - 3); - }; - - if let Some(g) = matches.get_one::(OPT_GOAL) { - goal = *g; - if !matches.contains_id(OPT_WIDTH) { - width = cmp::max(goal * 100 / DEFAULT_GOAL_TO_WIDTH_RATIO, goal + 3); - } else if goal > width { - return Err(USimpleError::new(1, "GOAL cannot be greater than WIDTH.")); + (Some(&w), None) => { + let g = (w * DEFAULT_GOAL_TO_WIDTH_RATIO / 100).min(w - 3); + (w, g) } + (None, Some(&g)) => { + let w = (g * 100 / DEFAULT_GOAL_TO_WIDTH_RATIO).max(g + 3); + (w, g) + } + (None, None) => (75, 70), }; + if width > MAX_WIDTH { + return Err(USimpleError::new( + 1, + format!("invalid width: '{}': Numerical result out of range", width), + )); + } + let mut tabwidth = 8; if let Some(s) = matches.get_one::(OPT_TAB_WIDTH) { tabwidth = match s.parse::() { From 8a494530572ca1a2221416a284144a9e44177f8e Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Tue, 28 Nov 2023 12:24:18 +0100 Subject: [PATCH 0415/2851] fmt: clean up imports --- src/uu/fmt/src/fmt.rs | 4 ++-- src/uu/fmt/src/linebreak.rs | 4 +--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/uu/fmt/src/fmt.rs b/src/uu/fmt/src/fmt.rs index 0ed32641fbb..3461a79ba7f 100644 --- a/src/uu/fmt/src/fmt.rs +++ b/src/uu/fmt/src/fmt.rs @@ -13,8 +13,8 @@ use uucore::display::Quotable; use uucore::error::{FromIo, UResult, USimpleError}; use uucore::{format_usage, help_about, help_usage, show_warning}; -use self::linebreak::break_lines; -use self::parasplit::ParagraphStream; +use linebreak::break_lines; +use parasplit::ParagraphStream; mod linebreak; mod parasplit; diff --git a/src/uu/fmt/src/linebreak.rs b/src/uu/fmt/src/linebreak.rs index 7cd65d86149..306c15f3614 100644 --- a/src/uu/fmt/src/linebreak.rs +++ b/src/uu/fmt/src/linebreak.rs @@ -5,10 +5,8 @@ // spell-checker:ignore (ToDO) INFTY MULT accum breakwords linebreak linebreaking linebreaks linelen maxlength minlength nchars ostream overlen parasplit plass posn powf punct signum slen sstart tabwidth tlen underlen winfo wlen wordlen -use std::cmp; -use std::i64; use std::io::{BufWriter, Stdout, Write}; -use std::mem; +use std::{cmp, i64, mem}; use uucore::crash; From 0b4d4b610cc510a7aff4095447ddf8195cf27072 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Tue, 28 Nov 2023 12:30:01 +0100 Subject: [PATCH 0416/2851] fmt: put options into module and change static to const --- src/uu/fmt/src/fmt.rs | 100 +++++++++++++++++++++--------------------- 1 file changed, 51 insertions(+), 49 deletions(-) diff --git a/src/uu/fmt/src/fmt.rs b/src/uu/fmt/src/fmt.rs index 3461a79ba7f..e44b7e0e5be 100644 --- a/src/uu/fmt/src/fmt.rs +++ b/src/uu/fmt/src/fmt.rs @@ -7,8 +7,7 @@ use clap::{crate_version, Arg, ArgAction, ArgMatches, Command}; use std::fs::File; -use std::io::{stdin, stdout, Write}; -use std::io::{BufReader, BufWriter, Read, Stdout}; +use std::io::{stdin, stdout, BufReader, BufWriter, Read, Stdout, Write}; use uucore::display::Quotable; use uucore::error::{FromIo, UResult, USimpleError}; use uucore::{format_usage, help_about, help_usage, show_warning}; @@ -19,25 +18,26 @@ use parasplit::ParagraphStream; mod linebreak; mod parasplit; -static ABOUT: &str = help_about!("fmt.md"); +const ABOUT: &str = help_about!("fmt.md"); const USAGE: &str = help_usage!("fmt.md"); -static MAX_WIDTH: usize = 2500; - -static OPT_CROWN_MARGIN: &str = "crown-margin"; -static OPT_TAGGED_PARAGRAPH: &str = "tagged-paragraph"; -static OPT_PRESERVE_HEADERS: &str = "preserve-headers"; -static OPT_SPLIT_ONLY: &str = "split-only"; -static OPT_UNIFORM_SPACING: &str = "uniform-spacing"; -static OPT_PREFIX: &str = "prefix"; -static OPT_SKIP_PREFIX: &str = "skip-prefix"; -static OPT_EXACT_PREFIX: &str = "exact-prefix"; -static OPT_EXACT_SKIP_PREFIX: &str = "exact-skip-prefix"; -static OPT_WIDTH: &str = "width"; -static OPT_GOAL: &str = "goal"; -static OPT_QUICK: &str = "quick"; -static OPT_TAB_WIDTH: &str = "tab-width"; - -static ARG_FILES: &str = "files"; +const MAX_WIDTH: usize = 2500; + +mod options { + pub const CROWN_MARGIN: &str = "crown-margin"; + pub const TAGGED_PARAGRAPH: &str = "tagged-paragraph"; + pub const PRESERVE_HEADERS: &str = "preserve-headers"; + pub const SPLIT_ONLY: &str = "split-only"; + pub const UNIFORM_SPACING: &str = "uniform-spacing"; + pub const PREFIX: &str = "prefix"; + pub const SKIP_PREFIX: &str = "skip-prefix"; + pub const EXACT_PREFIX: &str = "exact-prefix"; + pub const EXACT_SKIP_PREFIX: &str = "exact-skip-prefix"; + pub const WIDTH: &str = "width"; + pub const GOAL: &str = "goal"; + pub const QUICK: &str = "quick"; + pub const TAB_WIDTH: &str = "tab-width"; + pub const FILES: &str = "files"; +} // by default, goal is 93% of width const DEFAULT_GOAL_TO_WIDTH_RATIO: usize = 93; @@ -61,13 +61,13 @@ pub struct FmtOptions { impl FmtOptions { fn from_matches(matches: &ArgMatches) -> UResult { - let mut tagged = matches.get_flag(OPT_TAGGED_PARAGRAPH); - let mut crown = matches.get_flag(OPT_CROWN_MARGIN); + let mut tagged = matches.get_flag(options::TAGGED_PARAGRAPH); + let mut crown = matches.get_flag(options::CROWN_MARGIN); - let mail = matches.get_flag(OPT_PRESERVE_HEADERS); - let uniform = matches.get_flag(OPT_UNIFORM_SPACING); - let quick = matches.get_flag(OPT_QUICK); - let split_only = matches.get_flag(OPT_SPLIT_ONLY); + let mail = matches.get_flag(options::PRESERVE_HEADERS); + let uniform = matches.get_flag(options::UNIFORM_SPACING); + let quick = matches.get_flag(options::QUICK); + let split_only = matches.get_flag(options::SPLIT_ONLY); if crown { tagged = false; @@ -77,14 +77,16 @@ impl FmtOptions { tagged = false; } - let xprefix = matches.contains_id(OPT_EXACT_PREFIX); - let xanti_prefix = matches.contains_id(OPT_SKIP_PREFIX); + let xprefix = matches.contains_id(options::EXACT_PREFIX); + let xanti_prefix = matches.contains_id(options::SKIP_PREFIX); - let prefix = matches.get_one::(OPT_PREFIX).map(String::from); - let anti_prefix = matches.get_one::(OPT_SKIP_PREFIX).map(String::from); + let prefix = matches.get_one::(options::PREFIX).map(String::from); + let anti_prefix = matches + .get_one::(options::SKIP_PREFIX) + .map(String::from); - let width_opt = matches.get_one::(OPT_WIDTH); - let goal_opt = matches.get_one::(OPT_GOAL); + let width_opt = matches.get_one::(options::WIDTH); + let goal_opt = matches.get_one::(options::GOAL); let (width, goal) = match (width_opt, goal_opt) { (Some(&w), Some(&g)) => { if g > w { @@ -111,7 +113,7 @@ impl FmtOptions { } let mut tabwidth = 8; - if let Some(s) = matches.get_one::(OPT_TAB_WIDTH) { + if let Some(s) = matches.get_one::(options::TAB_WIDTH) { tabwidth = match s.parse::() { Ok(t) => t, Err(e) => { @@ -158,7 +160,7 @@ fn parse_arguments(args: impl uucore::Args) -> UResult<(Vec, FmtOptions) let matches = uu_app().try_get_matches_from(args)?; let mut files: Vec = matches - .get_many::(ARG_FILES) + .get_many::(options::FILES) .map(|v| v.map(ToString::to_string).collect()) .unwrap_or_default(); @@ -242,9 +244,9 @@ pub fn uu_app() -> Command { .override_usage(format_usage(USAGE)) .infer_long_args(true) .arg( - Arg::new(OPT_CROWN_MARGIN) + Arg::new(options::CROWN_MARGIN) .short('c') - .long(OPT_CROWN_MARGIN) + .long(options::CROWN_MARGIN) .help( "First and second line of paragraph \ may have different indentations, in which \ @@ -254,7 +256,7 @@ pub fn uu_app() -> Command { .action(ArgAction::SetTrue), ) .arg( - Arg::new(OPT_TAGGED_PARAGRAPH) + Arg::new(options::TAGGED_PARAGRAPH) .short('t') .long("tagged-paragraph") .help( @@ -264,7 +266,7 @@ pub fn uu_app() -> Command { .action(ArgAction::SetTrue), ) .arg( - Arg::new(OPT_PRESERVE_HEADERS) + Arg::new(options::PRESERVE_HEADERS) .short('m') .long("preserve-headers") .help( @@ -274,14 +276,14 @@ pub fn uu_app() -> Command { .action(ArgAction::SetTrue), ) .arg( - Arg::new(OPT_SPLIT_ONLY) + Arg::new(options::SPLIT_ONLY) .short('s') .long("split-only") .help("Split lines only, do not reflow.") .action(ArgAction::SetTrue), ) .arg( - Arg::new(OPT_UNIFORM_SPACING) + Arg::new(options::UNIFORM_SPACING) .short('u') .long("uniform-spacing") .help( @@ -294,7 +296,7 @@ pub fn uu_app() -> Command { .action(ArgAction::SetTrue), ) .arg( - Arg::new(OPT_PREFIX) + Arg::new(options::PREFIX) .short('p') .long("prefix") .help( @@ -306,7 +308,7 @@ pub fn uu_app() -> Command { .value_name("PREFIX"), ) .arg( - Arg::new(OPT_SKIP_PREFIX) + Arg::new(options::SKIP_PREFIX) .short('P') .long("skip-prefix") .help( @@ -317,7 +319,7 @@ pub fn uu_app() -> Command { .value_name("PSKIP"), ) .arg( - Arg::new(OPT_EXACT_PREFIX) + Arg::new(options::EXACT_PREFIX) .short('x') .long("exact-prefix") .help( @@ -327,7 +329,7 @@ pub fn uu_app() -> Command { .action(ArgAction::SetTrue), ) .arg( - Arg::new(OPT_EXACT_SKIP_PREFIX) + Arg::new(options::EXACT_SKIP_PREFIX) .short('X') .long("exact-skip-prefix") .help( @@ -337,7 +339,7 @@ pub fn uu_app() -> Command { .action(ArgAction::SetTrue), ) .arg( - Arg::new(OPT_WIDTH) + Arg::new(options::WIDTH) .short('w') .long("width") .help("Fill output lines up to a maximum of WIDTH columns, default 75.") @@ -345,7 +347,7 @@ pub fn uu_app() -> Command { .value_parser(clap::value_parser!(usize)), ) .arg( - Arg::new(OPT_GOAL) + Arg::new(options::GOAL) .short('g') .long("goal") .help("Goal width, default of 93% of WIDTH. Must be less than WIDTH.") @@ -353,7 +355,7 @@ pub fn uu_app() -> Command { .value_parser(clap::value_parser!(usize)), ) .arg( - Arg::new(OPT_QUICK) + Arg::new(options::QUICK) .short('q') .long("quick") .help( @@ -363,7 +365,7 @@ pub fn uu_app() -> Command { .action(ArgAction::SetTrue), ) .arg( - Arg::new(OPT_TAB_WIDTH) + Arg::new(options::TAB_WIDTH) .short('T') .long("tab-width") .help( @@ -374,7 +376,7 @@ pub fn uu_app() -> Command { .value_name("TABWIDTH"), ) .arg( - Arg::new(ARG_FILES) + Arg::new(options::FILES) .action(ArgAction::Append) .value_hint(clap::ValueHint::FilePath), ) From 2a8f4ec294369c228bc8063676879a9ac9e436e5 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Tue, 28 Nov 2023 12:34:04 +0100 Subject: [PATCH 0417/2851] fmt: inline parse_arguments function --- src/uu/fmt/src/fmt.rs | 35 ++++++++--------------------------- 1 file changed, 8 insertions(+), 27 deletions(-) diff --git a/src/uu/fmt/src/fmt.rs b/src/uu/fmt/src/fmt.rs index e44b7e0e5be..4380487814b 100644 --- a/src/uu/fmt/src/fmt.rs +++ b/src/uu/fmt/src/fmt.rs @@ -147,32 +147,6 @@ impl FmtOptions { } } -/// Parse the command line arguments and return the list of files and formatting options. -/// -/// # Arguments -/// -/// * `args` - Command line arguments. -/// -/// # Returns -/// -/// A tuple containing a vector of file names and a `FmtOptions` struct. -fn parse_arguments(args: impl uucore::Args) -> UResult<(Vec, FmtOptions)> { - let matches = uu_app().try_get_matches_from(args)?; - - let mut files: Vec = matches - .get_many::(options::FILES) - .map(|v| v.map(ToString::to_string).collect()) - .unwrap_or_default(); - - let fmt_opts = FmtOptions::from_matches(&matches)?; - - if files.is_empty() { - files.push("-".to_owned()); - } - - Ok((files, fmt_opts)) -} - /// Process the content of a file and format it according to the provided options. /// /// # Arguments @@ -226,7 +200,14 @@ fn process_file( #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let (files, fmt_opts) = parse_arguments(args)?; + let matches = uu_app().try_get_matches_from(args)?; + + let files: Vec = matches + .get_many::(options::FILES) + .map(|v| v.map(ToString::to_string).collect()) + .unwrap_or(vec!["-".into()]); + + let fmt_opts = FmtOptions::from_matches(&matches)?; let mut ostream = BufWriter::new(stdout()); From 2d5ea264106b26caea8133f8d3d1c31ade2ac3ba Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Tue, 28 Nov 2023 14:33:19 +0100 Subject: [PATCH 0418/2851] fmt: rename pfxind_end -> prefix_indent_end --- src/uu/fmt/src/parasplit.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/uu/fmt/src/parasplit.rs b/src/uu/fmt/src/parasplit.rs index f22400dff20..1ae8ea34f42 100644 --- a/src/uu/fmt/src/parasplit.rs +++ b/src/uu/fmt/src/parasplit.rs @@ -3,7 +3,7 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// spell-checker:ignore (ToDO) INFTY MULT PSKIP accum aftertab beforetab breakwords fmt's formatline linebreak linebreaking linebreaks linelen maxlength minlength nchars noformat noformatline ostream overlen parasplit pfxind plass pmatch poffset posn powf prefixindent punct signum slen sstart tabwidth tlen underlen winfo wlen wordlen wordsplits xanti xprefix +// spell-checker:ignore (ToDO) INFTY MULT PSKIP accum aftertab beforetab breakwords fmt's formatline linebreak linebreaking linebreaks linelen maxlength minlength nchars noformat noformatline ostream overlen parasplit plass pmatch poffset posn powf prefixindent punct signum slen sstart tabwidth tlen underlen winfo wlen wordlen wordsplits xanti xprefix use std::io::{BufRead, Lines}; use std::iter::Peekable; @@ -60,7 +60,7 @@ pub struct FileLine { /// The end of the indent, always the start of the text indent_end: usize, /// The end of the PREFIX's indent, that is, the spaces before the prefix - pfxind_end: usize, + prefix_indent_end: usize, /// Display length of indent taking into account tabs indent_len: usize, /// PREFIX indent length taking into account tabs @@ -192,7 +192,7 @@ impl<'a> Iterator for FileLines<'a> { Some(Line::FormatLine(FileLine { line: n, indent_end, - pfxind_end: poffset, + prefix_indent_end: poffset, indent_len, prefix_len, })) @@ -210,7 +210,7 @@ pub struct Paragraph { lines: Vec, /// string representing the init, that is, the first line's indent pub init_str: String, - /// printable length of the init string considering TABWIDTH + /// printable length of the init string considering TABWIDTH pub init_len: usize, /// byte location of end of init in first line String init_end: usize, @@ -299,7 +299,7 @@ impl<'a> Iterator for ParagraphStream<'a> { let mut indent_end = 0; let mut indent_len = 0; let mut prefix_len = 0; - let mut pfxind_end = 0; + let mut prefix_indent_end = 0; let mut p_lines = Vec::new(); let mut in_mail = false; @@ -316,7 +316,7 @@ impl<'a> Iterator for ParagraphStream<'a> { // detect mail header if self.opts.mail && self.next_mail && ParagraphStream::is_mail_header(fl) { in_mail = true; - // there can't be any indent or pfxind because otherwise is_mail_header + // there can't be any indent or prefixindent because otherwise is_mail_header // would fail since there cannot be any whitespace before the colon in a // valid header field indent_str.push_str(" "); @@ -339,7 +339,7 @@ impl<'a> Iterator for ParagraphStream<'a> { // save these to check for matching lines prefix_len = fl.prefix_len; - pfxind_end = fl.pfxind_end; + prefix_indent_end = fl.prefix_indent_end; // in tagged mode, add 4 spaces of additional indenting by default // (gnu fmt's behavior is different: it seems to find the closest column to @@ -353,14 +353,14 @@ impl<'a> Iterator for ParagraphStream<'a> { } } else if in_mail { // lines following mail headers must begin with spaces - if fl.indent_end == 0 || (self.opts.prefix.is_some() && fl.pfxind_end == 0) { + if fl.indent_end == 0 || (self.opts.prefix.is_some() && fl.prefix_indent_end == 0) { break; // this line does not begin with spaces } } else if !second_done { // now we have enough info to handle crown margin and tagged mode // in both crown and tagged modes we require that prefix_len is the same - if prefix_len != fl.prefix_len || pfxind_end != fl.pfxind_end { + if prefix_len != fl.prefix_len || prefix_indent_end != fl.prefix_indent_end { break; } @@ -382,7 +382,7 @@ impl<'a> Iterator for ParagraphStream<'a> { } else { // detect mismatch if indent_end != fl.indent_end - || pfxind_end != fl.pfxind_end + || prefix_indent_end != fl.prefix_indent_end || indent_len != fl.indent_len || prefix_len != fl.prefix_len { From 7383820354d497b647bd721f3b147d9700676847 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dylan=20A=C3=AFssi?= Date: Tue, 28 Nov 2023 16:51:20 +0100 Subject: [PATCH 0419/2851] uuhelp_parser: include missing LICENSE MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Dylan Aïssi --- src/uuhelp_parser/LICENSE | 1 + 1 file changed, 1 insertion(+) create mode 120000 src/uuhelp_parser/LICENSE diff --git a/src/uuhelp_parser/LICENSE b/src/uuhelp_parser/LICENSE new file mode 120000 index 00000000000..30cff7403da --- /dev/null +++ b/src/uuhelp_parser/LICENSE @@ -0,0 +1 @@ +../../LICENSE \ No newline at end of file From 6eec4fe8f9ce40e95199de2fc67170d2cd11bfae Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Wed, 29 Nov 2023 09:35:22 +0100 Subject: [PATCH 0420/2851] cut: add test & improve error message --- src/uu/cut/src/cut.rs | 2 +- tests/by-util/test_cut.rs | 11 ++++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/uu/cut/src/cut.rs b/src/uu/cut/src/cut.rs index 05e8bc6e424..0555be14f73 100644 --- a/src/uu/cut/src/cut.rs +++ b/src/uu/cut/src/cut.rs @@ -426,7 +426,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { delim = ""; } if delim.chars().count() > 1 { - Err("invalid input: The '--delimiter' ('-d') option expects empty or 1 character long, but was provided a value 2 characters or longer".into()) + Err("the delimiter must be a single character".into()) } else { let delim = if delim.is_empty() { "\0".to_owned() diff --git a/tests/by-util/test_cut.rs b/tests/by-util/test_cut.rs index 184e413a867..112dc0fd3e5 100644 --- a/tests/by-util/test_cut.rs +++ b/tests/by-util/test_cut.rs @@ -126,7 +126,7 @@ fn test_too_large() { } #[test] -fn test_specify_delimiter() { +fn test_delimiter() { for param in ["-d", "--delimiter", "--del"] { new_ucmd!() .args(&[param, ":", "-f", COMPLEX_SEQUENCE.sequence, INPUT]) @@ -135,6 +135,15 @@ fn test_specify_delimiter() { } } +#[test] +fn test_delimiter_with_more_than_one_char() { + new_ucmd!() + .args(&["-d", "ab", "-f1"]) + .fails() + .stderr_contains("cut: the delimiter must be a single character") + .no_stdout(); +} + #[test] fn test_output_delimiter() { // we use -d here to ensure output delimiter From 66e0835e72a72d8ff0a97213b5320fc45973ecbc Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Wed, 29 Nov 2023 09:55:55 +0100 Subject: [PATCH 0421/2851] fuzz the echo command --- fuzz/Cargo.toml | 7 +++ fuzz/fuzz_targets/fuzz_echo.rs | 93 ++++++++++++++++++++++++++++++++++ 2 files changed, 100 insertions(+) create mode 100644 fuzz/fuzz_targets/fuzz_echo.rs diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index 630af4650b6..b27f5b58677 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -17,6 +17,7 @@ uu_date = { path = "../src/uu/date/" } uu_test = { path = "../src/uu/test/" } uu_expr = { path = "../src/uu/expr/" } uu_printf = { path = "../src/uu/printf/" } +uu_echo = { path = "../src/uu/echo/" } # Prevent this from interfering with workspaces @@ -35,6 +36,12 @@ path = "fuzz_targets/fuzz_printf.rs" test = false doc = false +[[bin]] +name = "fuzz_echo" +path = "fuzz_targets/fuzz_echo.rs" +test = false +doc = false + [[bin]] name = "fuzz_expr" path = "fuzz_targets/fuzz_expr.rs" diff --git a/fuzz/fuzz_targets/fuzz_echo.rs b/fuzz/fuzz_targets/fuzz_echo.rs new file mode 100644 index 00000000000..3d810085301 --- /dev/null +++ b/fuzz/fuzz_targets/fuzz_echo.rs @@ -0,0 +1,93 @@ +#![no_main] +use libfuzzer_sys::fuzz_target; +use uu_echo::uumain; // Changed from uu_printf to uu_echo + +use rand::prelude::SliceRandom; +use rand::Rng; +use std::ffi::OsString; + +mod fuzz_common; +use crate::fuzz_common::CommandResult; +use crate::fuzz_common::{ + compare_result, generate_and_run_uumain, generate_random_string, run_gnu_cmd, +}; + +static CMD_PATH: &str = "/usr/bin/echo"; // Changed from "printf" to "echo" + +fn generate_echo() -> String { + let mut rng = rand::thread_rng(); + let mut echo_str = String::new(); + + // Randomly decide whether to include options + let include_n = rng.gen_bool(0.1); // 10% chance + let include_e = rng.gen_bool(0.1); // 10% chance + let include_E = rng.gen_bool(0.1); // 10% chance + // --help and --version are typically not included in fuzzing as they don't change output format + + if include_n { + echo_str.push_str("-n "); + } + if include_e { + echo_str.push_str("-e "); + } + if include_E { + echo_str.push_str("-E "); + } + + // Add a random string + echo_str.push_str(&generate_random_string(rng.gen_range(1..=10))); + + // Include escape sequences if -e is enabled + if include_e { + // Add a 10% chance of including an escape sequence + if rng.gen_bool(0.1) { + echo_str.push_str(&generate_escape_sequence(&mut rng)); // This function should handle echo-specific sequences + } + } + + echo_str +} + +// You should also modify the generate_escape_sequence function to include echo-specific sequences +fn generate_escape_sequence(rng: &mut impl Rng) -> String { + let escape_sequences = [ + "\\\\", "\\a", "\\b", "\\c", "\\e", "\\f", "\\n", "\\r", "\\t", "\\v", + "\\0NNN", // You can randomly generate NNN + "\\xHH", // You can randomly generate HH + // ... other sequences + ]; + escape_sequences.choose(rng).unwrap().to_string() +} + +fuzz_target!(|_data: &[u8]| { + let echo_input = generate_echo(); // Changed from generate_printf to generate_echo + let mut args = vec![OsString::from("echo")]; // Changed from "printf" to "echo" + args.extend(echo_input.split_whitespace().map(OsString::from)); + let rust_result = generate_and_run_uumain(&args, uumain); // uumain function from uu_echo + + let gnu_result = match run_gnu_cmd(CMD_PATH, &args[1..], false) { + Ok(result) => result, + Err(error_result) => { + eprintln!("Failed to run GNU command:"); + eprintln!("Stderr: {}", error_result.stderr); + eprintln!("Exit Code: {}", error_result.exit_code); + CommandResult { + stdout: String::new(), + stderr: error_result.stderr, + exit_code: error_result.exit_code, + } + } + }; + + compare_result( + "echo", + &format!("{:?}", &args[1..]), + &rust_result.stdout, + &gnu_result.stdout, + &rust_result.stderr, + &gnu_result.stderr, + rust_result.exit_code, + gnu_result.exit_code, + false, // Set to true if you want to fail on stderr diff + ); +}); From 203e79d74b406a02ab7202f36c66ed148f11411c Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Wed, 29 Nov 2023 14:09:13 +0100 Subject: [PATCH 0422/2851] Fix unused import: `UError` --- src/uu/kill/src/kill.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/kill/src/kill.rs b/src/uu/kill/src/kill.rs index b0e18a79820..f353fd7cab3 100644 --- a/src/uu/kill/src/kill.rs +++ b/src/uu/kill/src/kill.rs @@ -10,7 +10,7 @@ use nix::sys::signal::{self, Signal}; use nix::unistd::Pid; use std::io::Error; use uucore::display::Quotable; -use uucore::error::{FromIo, UError, UResult, USimpleError}; +use uucore::error::{FromIo, UResult, USimpleError}; use uucore::signals::{signal_by_name_or_value, ALL_SIGNALS}; use uucore::{format_usage, help_about, help_usage, show}; From 420df3db3d14f89c1b11278fc485aac7fd1d9745 Mon Sep 17 00:00:00 2001 From: Laurent Cheylus Date: Wed, 29 Nov 2023 17:44:50 +0100 Subject: [PATCH 0423/2851] Add support in uucore for OpenBSD - uucore/src/lib/features/fs.rs: add target_os = OpenBSD when needed - uucore/src/lib/features/fsext.rs: implement FsUsage::new for OpenBSD - fixes uutils/coreutils#5448 - initial code by n1000 https://github.com/n1000/coreutils/tree/openbsd_compile_fixes Signed-off-by: Laurent Cheylus --- src/uucore/src/lib/features/fs.rs | 5 +++- src/uucore/src/lib/features/fsext.rs | 39 ++++++++++++++++++++++++---- 2 files changed, 38 insertions(+), 6 deletions(-) diff --git a/src/uucore/src/lib/features/fs.rs b/src/uucore/src/lib/features/fs.rs index de4c0b08dbe..94ca82c8098 100644 --- a/src/uucore/src/lib/features/fs.rs +++ b/src/uucore/src/lib/features/fs.rs @@ -115,6 +115,7 @@ impl FileInformation { not(target_os = "android"), not(target_os = "freebsd"), not(target_os = "netbsd"), + not(target_os = "openbsd"), not(target_os = "illumos"), not(target_os = "solaris"), not(target_arch = "aarch64"), @@ -130,6 +131,7 @@ impl FileInformation { target_os = "android", target_os = "freebsd", target_os = "netbsd", + target_os = "openbsd", target_os = "illumos", target_os = "solaris", target_arch = "aarch64", @@ -146,13 +148,14 @@ impl FileInformation { #[cfg(unix)] pub fn inode(&self) -> u64 { #[cfg(all( - not(any(target_os = "freebsd", target_os = "netbsd")), + not(any(target_os = "freebsd", target_os = "netbsd", target_os = "openbsd")), target_pointer_width = "64" ))] return self.0.st_ino; #[cfg(any( target_os = "freebsd", target_os = "netbsd", + target_os = "openbsd", not(target_pointer_width = "64") ))] return self.0.st_ino.into(); diff --git a/src/uucore/src/lib/features/fsext.rs b/src/uucore/src/lib/features/fsext.rs index 8b1c42de6d1..93fedb44b7c 100644 --- a/src/uucore/src/lib/features/fsext.rs +++ b/src/uucore/src/lib/features/fsext.rs @@ -497,7 +497,10 @@ impl FsUsage { #[cfg(unix)] pub fn new(statvfs: StatFs) -> Self { { - #[cfg(all(not(target_os = "freebsd"), target_pointer_width = "64"))] + #[cfg(all( + not(any(target_os = "freebsd", target_os = "openbsd")), + target_pointer_width = "64" + ))] return Self { blocksize: statvfs.f_bsize as u64, // or `statvfs.f_frsize` ? blocks: statvfs.f_blocks, @@ -507,7 +510,10 @@ impl FsUsage { files: statvfs.f_files, ffree: statvfs.f_ffree, }; - #[cfg(all(not(target_os = "freebsd"), not(target_pointer_width = "64")))] + #[cfg(all( + not(any(target_os = "freebsd", target_os = "openbsd")), + not(target_pointer_width = "64") + ))] return Self { blocksize: statvfs.f_bsize as u64, // or `statvfs.f_frsize` ? blocks: statvfs.f_blocks.into(), @@ -530,6 +536,19 @@ impl FsUsage { files: statvfs.f_files, ffree: statvfs.f_ffree.try_into().unwrap(), }; + #[cfg(target_os = "openbsd")] + return Self { + blocksize: statvfs.f_bsize.into(), + blocks: statvfs.f_blocks, + bfree: statvfs.f_bfree, + bavail: statvfs.f_bavail.try_into().unwrap(), + bavail_top_bit_set: ((std::convert::TryInto::::try_into(statvfs.f_bavail) + .unwrap()) + & (1u64.rotate_right(1))) + != 0, + files: statvfs.f_files, + ffree: statvfs.f_ffree, + }; } } #[cfg(not(unix))] @@ -617,6 +636,7 @@ impl FsMeta for StatFs { not(target_vendor = "apple"), not(target_os = "android"), not(target_os = "freebsd"), + not(target_os = "openbsd"), not(target_os = "illumos"), not(target_os = "solaris"), not(target_arch = "s390x"), @@ -630,6 +650,7 @@ impl FsMeta for StatFs { target_arch = "s390x", target_vendor = "apple", target_os = "android", + target_os = "openbsd", not(target_pointer_width = "64") ) ))] @@ -655,11 +676,19 @@ impl FsMeta for StatFs { return self.f_bfree.into(); } fn avail_blocks(&self) -> u64 { - #[cfg(all(not(target_os = "freebsd"), target_pointer_width = "64"))] + #[cfg(all( + not(target_os = "freebsd"), + not(target_os = "openbsd"), + target_pointer_width = "64" + ))] return self.f_bavail; - #[cfg(all(not(target_os = "freebsd"), not(target_pointer_width = "64")))] + #[cfg(all( + not(target_os = "freebsd"), + not(target_os = "openbsd"), + not(target_pointer_width = "64") + ))] return self.f_bavail.into(); - #[cfg(target_os = "freebsd")] + #[cfg(any(target_os = "freebsd", target_os = "openbsd"))] return self.f_bavail.try_into().unwrap(); } fn total_file_nodes(&self) -> u64 { From 173153122b255bb57afbbad1872a26ebe9050c16 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 29 Nov 2023 21:49:08 +0000 Subject: [PATCH 0424/2851] chore(deps): update rust crate lscolors to 0.16.0 --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d7682f839e4..3f5e4280964 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1240,9 +1240,9 @@ dependencies = [ [[package]] name = "lscolors" -version = "0.15.0" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf7015a04103ad78abb77e4b79ed151e767922d1cfde5f62640471c629a2320d" +checksum = "ab0b209ec3976527806024406fe765474b9a1750a0ed4b8f0372364741f50e7b" dependencies = [ "nu-ansi-term", ] diff --git a/Cargo.toml b/Cargo.toml index ba701b2d5e5..14e700ee0a6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -287,7 +287,7 @@ half = "2.3" indicatif = "0.17" itertools = "0.12.0" libc = "0.2.150" -lscolors = { version = "0.15.0", default-features = false, features = [ +lscolors = { version = "0.16.0", default-features = false, features = [ "nu-ansi-term", ] } memchr = "2" From 4d5c034eb10d0a07098abb75ddd5771b6d444c05 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Wed, 29 Nov 2023 14:11:43 +0100 Subject: [PATCH 0425/2851] Run the echo fuzzer in the CI --- .github/workflows/fuzzing.yml | 1 + fuzz/fuzz_targets/fuzz_echo.rs | 21 ++++++++------------- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/.github/workflows/fuzzing.yml b/.github/workflows/fuzzing.yml index 2274f6905c1..cc1547c87a6 100644 --- a/.github/workflows/fuzzing.yml +++ b/.github/workflows/fuzzing.yml @@ -42,6 +42,7 @@ jobs: - { name: fuzz_date, should_pass: false } - { name: fuzz_expr, should_pass: true } - { name: fuzz_printf, should_pass: false } + - { name: fuzz_echo, should_pass: false } - { name: fuzz_parse_glob, should_pass: true } - { name: fuzz_parse_size, should_pass: true } - { name: fuzz_parse_time, should_pass: true } diff --git a/fuzz/fuzz_targets/fuzz_echo.rs b/fuzz/fuzz_targets/fuzz_echo.rs index 3d810085301..826fd6da3eb 100644 --- a/fuzz/fuzz_targets/fuzz_echo.rs +++ b/fuzz/fuzz_targets/fuzz_echo.rs @@ -1,6 +1,6 @@ #![no_main] use libfuzzer_sys::fuzz_target; -use uu_echo::uumain; // Changed from uu_printf to uu_echo +use uu_echo::uumain; use rand::prelude::SliceRandom; use rand::Rng; @@ -12,7 +12,7 @@ use crate::fuzz_common::{ compare_result, generate_and_run_uumain, generate_random_string, run_gnu_cmd, }; -static CMD_PATH: &str = "/usr/bin/echo"; // Changed from "printf" to "echo" +static CMD_PATH: &str = "echo"; fn generate_echo() -> String { let mut rng = rand::thread_rng(); @@ -22,7 +22,6 @@ fn generate_echo() -> String { let include_n = rng.gen_bool(0.1); // 10% chance let include_e = rng.gen_bool(0.1); // 10% chance let include_E = rng.gen_bool(0.1); // 10% chance - // --help and --version are typically not included in fuzzing as they don't change output format if include_n { echo_str.push_str("-n "); @@ -41,29 +40,25 @@ fn generate_echo() -> String { if include_e { // Add a 10% chance of including an escape sequence if rng.gen_bool(0.1) { - echo_str.push_str(&generate_escape_sequence(&mut rng)); // This function should handle echo-specific sequences + echo_str.push_str(&generate_escape_sequence(&mut rng)); } } echo_str } -// You should also modify the generate_escape_sequence function to include echo-specific sequences fn generate_escape_sequence(rng: &mut impl Rng) -> String { let escape_sequences = [ - "\\\\", "\\a", "\\b", "\\c", "\\e", "\\f", "\\n", "\\r", "\\t", "\\v", - "\\0NNN", // You can randomly generate NNN - "\\xHH", // You can randomly generate HH - // ... other sequences + "\\\\", "\\a", "\\b", "\\c", "\\e", "\\f", "\\n", "\\r", "\\t", "\\v", "\\0NNN", "\\xHH", ]; escape_sequences.choose(rng).unwrap().to_string() } fuzz_target!(|_data: &[u8]| { - let echo_input = generate_echo(); // Changed from generate_printf to generate_echo - let mut args = vec![OsString::from("echo")]; // Changed from "printf" to "echo" + let echo_input = generate_echo(); + let mut args = vec![OsString::from("echo")]; args.extend(echo_input.split_whitespace().map(OsString::from)); - let rust_result = generate_and_run_uumain(&args, uumain); // uumain function from uu_echo + let rust_result = generate_and_run_uumain(&args, uumain); let gnu_result = match run_gnu_cmd(CMD_PATH, &args[1..], false) { Ok(result) => result, @@ -88,6 +83,6 @@ fuzz_target!(|_data: &[u8]| { &gnu_result.stderr, rust_result.exit_code, gnu_result.exit_code, - false, // Set to true if you want to fail on stderr diff + true, ); }); From 9061b2ba7e1267cd6c8466108ecb197614ce29ea Mon Sep 17 00:00:00 2001 From: clara swanson <69856940+cswn@users.noreply.github.com> Date: Thu, 30 Nov 2023 11:01:31 +0100 Subject: [PATCH 0426/2851] libstdbuf: remove crash macro (#5565) * libstdbuf: remove crash macro * libstdbuf: remove uucore macro/struct and use gnu messages * libstdbuf: remove crash macro * libstdbuf: remove uucore macro/struct and use gnu messages * libstdbuf: remove :? from print by printing file descriptor instead of file * merge main into libstdbuf-remove-crash-macro * libstdbuf: remove uucore from dependencies --- Cargo.lock | 1 - src/uu/stdbuf/src/libstdbuf/Cargo.toml | 1 - src/uu/stdbuf/src/libstdbuf/src/libstdbuf.rs | 14 ++++++++++---- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3f5e4280964..bf638b421a1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2952,7 +2952,6 @@ dependencies = [ "cpp", "cpp_build", "libc", - "uucore", ] [[package]] diff --git a/src/uu/stdbuf/src/libstdbuf/Cargo.toml b/src/uu/stdbuf/src/libstdbuf/Cargo.toml index be97c47aeaf..eaa82e6e73d 100644 --- a/src/uu/stdbuf/src/libstdbuf/Cargo.toml +++ b/src/uu/stdbuf/src/libstdbuf/Cargo.toml @@ -22,7 +22,6 @@ crate-type = [ [dependencies] cpp = "0.5" libc = { workspace = true } -uucore = { version = ">=0.0.19", package = "uucore", path = "../../../../uucore" } [build-dependencies] cpp_build = "0.5" diff --git a/src/uu/stdbuf/src/libstdbuf/src/libstdbuf.rs b/src/uu/stdbuf/src/libstdbuf/src/libstdbuf.rs index a29d01b78f3..d744ca4c545 100644 --- a/src/uu/stdbuf/src/libstdbuf/src/libstdbuf.rs +++ b/src/uu/stdbuf/src/libstdbuf/src/libstdbuf.rs @@ -5,10 +5,9 @@ // spell-checker:ignore (ToDO) IOFBF IOLBF IONBF cstdio setvbuf use cpp::cpp; -use libc::{c_char, c_int, size_t, FILE, _IOFBF, _IOLBF, _IONBF}; +use libc::{c_char, c_int, fileno, size_t, FILE, _IOFBF, _IOLBF, _IONBF}; use std::env; use std::ptr; -use uucore::crash; cpp! {{ #include @@ -40,7 +39,10 @@ fn set_buffer(stream: *mut FILE, value: &str) { input => { let buff_size: usize = match input.parse() { Ok(num) => num, - Err(e) => crash!(1, "incorrect size of buffer!: {}", e), + Err(_) => { + eprintln!("failed to allocate a {} byte stdio buffer", value); + std::process::exit(1); + } }; (_IOFBF, buff_size as size_t) } @@ -52,7 +54,11 @@ fn set_buffer(stream: *mut FILE, value: &str) { res = libc::setvbuf(stream, buffer, mode, size); } if res != 0 { - crash!(res, "error while calling setvbuf!"); + eprintln!( + "could not set buffering of {} to mode {}", + unsafe { fileno(stream) }, + mode + ); } } From 0ec6802459bf79d6f36c0289aa1cb262c40537ba Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Thu, 30 Nov 2023 16:13:54 +0100 Subject: [PATCH 0427/2851] ls: fix padding of size column when using -l --- src/uu/ls/src/ls.rs | 2 +- tests/by-util/test_ls.rs | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 88af56bb186..cba9cdf5375 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -3228,7 +3228,7 @@ fn calculate_padding_collection( padding_collections.minor = minor_len.max(padding_collections.minor); padding_collections.size = size_len .max(padding_collections.size) - .max(padding_collections.major + padding_collections.minor + 2usize); + .max(padding_collections.major); } } } diff --git a/tests/by-util/test_ls.rs b/tests/by-util/test_ls.rs index 07ea8c9cd63..19a3f5578fa 100644 --- a/tests/by-util/test_ls.rs +++ b/tests/by-util/test_ls.rs @@ -1023,6 +1023,21 @@ fn test_ls_long_format() { ).unwrap()); } +#[test] +fn test_ls_long_padding_of_size_column_with_multiple_files() { + let (at, mut ucmd) = at_and_ucmd!(); + + at.mkdir("dir"); + at.touch("dir/a"); + at.touch("dir/b"); + + ucmd.arg("-l") + .arg("dir") + .succeeds() + .stdout_contains(" 0 ") + .stdout_does_not_contain(" 0 "); +} + /// This test tests `ls -laR --color`. /// This test is mainly about coloring, but, the recursion, symlink `->` processing, /// and `.` and `..` being present in `-a` all need to work for the test to pass. From 8d591a7acce618ab3a9290f50487e9c0de22c5ed Mon Sep 17 00:00:00 2001 From: Piotr Kwiecinski Date: Thu, 30 Nov 2023 17:59:48 +0100 Subject: [PATCH 0428/2851] Bump freebsd-vm action to v1.0.2 & use ubuntu --- .github/workflows/freebsd.yml | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/.github/workflows/freebsd.yml b/.github/workflows/freebsd.yml index 5af3da320a7..b932f9aa8f5 100644 --- a/.github/workflows/freebsd.yml +++ b/.github/workflows/freebsd.yml @@ -25,7 +25,7 @@ jobs: fail-fast: false matrix: job: - - { os: macos-12 , features: unix } ## GHA MacOS-11.0 VM won't have VirtualBox; refs: , + - { os: ubuntu-22.04 , features: unix } env: SCCACHE_GHA_ENABLED: "true" RUSTC_WRAPPER: "sccache" @@ -35,9 +35,11 @@ jobs: - name: Run sccache-cache uses: mozilla-actions/sccache-action@v0.0.3 - name: Prepare, build and test - uses: vmactions/freebsd-vm@v0.3.1 + uses: vmactions/freebsd-vm@v1.0.2 with: usesh: true + sync: rsync + copyback: false # We need jq to run show-utils.sh and bash to use inline shell string replacement prepare: pkg install -y curl sudo jq bash run: | @@ -48,11 +50,11 @@ jobs: # TEST_USER=tester REPO_NAME=${GITHUB_WORKSPACE##*/} - WORKSPACE_PARENT="/Users/runner/work/${REPO_NAME}" + WORKSPACE_PARENT="/home/runner/work/${REPO_NAME}" WORKSPACE="${WORKSPACE_PARENT}/${REPO_NAME}" # pw adduser -n ${TEST_USER} -d /root/ -g wheel -c "Coreutils user to build" -w random - chown -R ${TEST_USER}:wheel /root/ "/Users/runner/work/${REPO_NAME}"/ + chown -R ${TEST_USER}:wheel /root/ "${WORKSPACE_PARENT}"/ whoami # # Further work needs to be done in a sudo as we are changing users @@ -114,7 +116,7 @@ jobs: fail-fast: false matrix: job: - - { os: macos-12 , features: unix } ## GHA MacOS-11.0 VM won't have VirtualBox; refs: , + - { os: ubuntu-22.04 , features: unix } env: mem: 4096 SCCACHE_GHA_ENABLED: "true" @@ -125,10 +127,11 @@ jobs: - name: Run sccache-cache uses: mozilla-actions/sccache-action@v0.0.3 - name: Prepare, build and test - uses: vmactions/freebsd-vm@v0.3.1 + uses: vmactions/freebsd-vm@v1.0.2 with: usesh: true - # sync: sshfs + sync: rsync + copyback: false prepare: pkg install -y curl gmake sudo run: | ## Prepare, build, and test @@ -141,12 +144,12 @@ jobs: # TEST_USER=tester REPO_NAME=${GITHUB_WORKSPACE##*/} - WORKSPACE_PARENT="/Users/runner/work/${REPO_NAME}" + WORKSPACE_PARENT="/home/runner/work/${REPO_NAME}" WORKSPACE="${WORKSPACE_PARENT}/${REPO_NAME}" # pw adduser -n ${TEST_USER} -d /root/ -g wheel -c "Coreutils user to build" -w random # chown -R ${TEST_USER}:wheel /root/ "${WORKSPACE_PARENT}"/ - chown -R ${TEST_USER}:wheel /root/ "/Users/runner/work/${REPO_NAME}"/ + chown -R ${TEST_USER}:wheel /root/ "${WORKSPACE_PARENT}"/ whoami # # Further work needs to be done in a sudo as we are changing users From c6e7fdcabe46e9bcd903567900c11d7e230f9c1f Mon Sep 17 00:00:00 2001 From: Piotr Kwiecinski Date: Thu, 30 Nov 2023 18:45:06 +0100 Subject: [PATCH 0429/2851] skip test_cp_arg_update_interactive on FreeBSD --- tests/by-util/test_cp.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/by-util/test_cp.rs b/tests/by-util/test_cp.rs index 14b68da3718..36bca68b0d0 100644 --- a/tests/by-util/test_cp.rs +++ b/tests/by-util/test_cp.rs @@ -257,6 +257,8 @@ fn test_cp_target_directory_is_file() { } #[test] +// FixMe: for FreeBSD, flaky test; track repair progress at GH:uutils/coreutils/issue/4725 +#[cfg(not(target_os = "freebsd"))] fn test_cp_arg_update_interactive() { new_ucmd!() .arg(TEST_HELLO_WORLD_SOURCE) From 6b3f00cc3192624f2e0f763fef668de3cc9ab25d Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Fri, 1 Dec 2023 10:03:44 +0100 Subject: [PATCH 0430/2851] dd: skip two tests without "printf" feature --- tests/by-util/test_dd.rs | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/tests/by-util/test_dd.rs b/tests/by-util/test_dd.rs index a4c70097c8b..bd43ad077d3 100644 --- a/tests/by-util/test_dd.rs +++ b/tests/by-util/test_dd.rs @@ -15,7 +15,12 @@ use regex::Regex; use std::fs::{File, OpenOptions}; use std::io::{BufReader, Read, Write}; use std::path::PathBuf; -#[cfg(all(unix, not(target_os = "macos"), not(target_os = "freebsd")))] +#[cfg(all( + unix, + not(target_os = "macos"), + not(target_os = "freebsd"), + feature = "printf" +))] use std::process::{Command, Stdio}; #[cfg(not(windows))] use std::thread::sleep; @@ -1586,7 +1591,12 @@ fn test_seek_past_dev() { } #[test] -#[cfg(all(unix, not(target_os = "macos"), not(target_os = "freebsd")))] +#[cfg(all( + unix, + not(target_os = "macos"), + not(target_os = "freebsd"), + feature = "printf" +))] fn test_reading_partial_blocks_from_fifo() { // Create the FIFO. let ts = TestScenario::new(util_name!()); @@ -1622,7 +1632,12 @@ fn test_reading_partial_blocks_from_fifo() { } #[test] -#[cfg(all(unix, not(target_os = "macos"), not(target_os = "freebsd")))] +#[cfg(all( + unix, + not(target_os = "macos"), + not(target_os = "freebsd"), + feature = "printf" +))] fn test_reading_partial_blocks_from_fifo_unbuffered() { // Create the FIFO. let ts = TestScenario::new(util_name!()); From 4d2bdf497ab030871a7a84191010637a355ffd7e Mon Sep 17 00:00:00 2001 From: Piotr Kwiecinski Date: Fri, 1 Dec 2023 11:52:23 +0100 Subject: [PATCH 0431/2851] prevent CI creating 2 events on each pull request push --- .github/workflows/CICD.yml | 6 +++++- .github/workflows/GnuTests.yml | 6 +++++- .github/workflows/android.yml | 7 ++++++- .github/workflows/code-quality.yml | 6 +++++- .github/workflows/freebsd.yml | 6 +++++- .github/workflows/fuzzing.yml | 6 +++++- 6 files changed, 31 insertions(+), 6 deletions(-) diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index 8dfa0b1d103..8c96ce693ee 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -15,7 +15,11 @@ env: # * style job configuration STYLE_FAIL_ON_FAULT: true ## (bool) fail the build if a style job contains a fault (error or warning); may be overridden on a per-job basis -on: [push, pull_request] +on: + pull_request: + push: + branches: + - main permissions: contents: read # to fetch code (actions/checkout) diff --git a/.github/workflows/GnuTests.yml b/.github/workflows/GnuTests.yml index 61f30eba4c1..87e4373eddc 100644 --- a/.github/workflows/GnuTests.yml +++ b/.github/workflows/GnuTests.yml @@ -9,7 +9,11 @@ name: GnuTests # * note: to run a single test => `REPO/util/run-gnu-test.sh PATH/TO/TEST/SCRIPT` -on: [push, pull_request] +on: + pull_request: + push: + branches: + - main permissions: contents: read diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index 5834aceffe8..69ca970c0fa 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -2,7 +2,12 @@ name: Android # spell-checker:ignore TERMUX reactivecircus Swatinem noaudio pkill swiftshader dtolnay juliangruber -on: [push, pull_request] +on: + pull_request: + push: + branches: + - main + permissions: contents: read # to fetch code (actions/checkout) diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml index 98691f34bc8..289830f8171 100644 --- a/.github/workflows/code-quality.yml +++ b/.github/workflows/code-quality.yml @@ -2,7 +2,11 @@ name: Code Quality # spell-checker:ignore TERMUX reactivecircus Swatinem noaudio pkill swiftshader dtolnay juliangruber -on: [push, pull_request] +on: + pull_request: + push: + branches: + - main env: # * style job configuration diff --git a/.github/workflows/freebsd.yml b/.github/workflows/freebsd.yml index b932f9aa8f5..02c0137e7b2 100644 --- a/.github/workflows/freebsd.yml +++ b/.github/workflows/freebsd.yml @@ -6,7 +6,11 @@ env: # * style job configuration STYLE_FAIL_ON_FAULT: true ## (bool) fail the build if a style job contains a fault (error or warning); may be overridden on a per-job basis -on: [push, pull_request] +on: + pull_request: + push: + branches: + - main permissions: contents: read # to fetch code (actions/checkout) diff --git a/.github/workflows/fuzzing.yml b/.github/workflows/fuzzing.yml index 2274f6905c1..4e47ea0e281 100644 --- a/.github/workflows/fuzzing.yml +++ b/.github/workflows/fuzzing.yml @@ -2,7 +2,11 @@ name: Fuzzing # spell-checker:ignore fuzzer -on: [push, pull_request] +on: + pull_request: + push: + branches: + - main permissions: contents: read # to fetch code (actions/checkout) From 13a4c9114c4574ee8d14ffe0bb6b326ad203375d Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sat, 2 Dec 2023 10:28:45 +0100 Subject: [PATCH 0432/2851] create datastructures for colors --- src/uucore/src/lib/features.rs | 2 + src/uucore/src/lib/features/colors.rs | 225 ++++++++++++++++++++++++++ 2 files changed, 227 insertions(+) create mode 100644 src/uucore/src/lib/features/colors.rs diff --git a/src/uucore/src/lib/features.rs b/src/uucore/src/lib/features.rs index 1d0d437824d..a28e8a7bfc9 100644 --- a/src/uucore/src/lib/features.rs +++ b/src/uucore/src/lib/features.rs @@ -6,6 +6,8 @@ #[cfg(feature = "backup-control")] pub mod backup_control; +#[cfg(feature = "colors")] +pub mod colors; #[cfg(feature = "encoding")] pub mod encoding; #[cfg(feature = "format")] diff --git a/src/uucore/src/lib/features/colors.rs b/src/uucore/src/lib/features/colors.rs new file mode 100644 index 00000000000..58b0b757084 --- /dev/null +++ b/src/uucore/src/lib/features/colors.rs @@ -0,0 +1,225 @@ +// This file is part of the uutils coreutils package. +// +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. + +/* The keywords COLOR, OPTIONS, and EIGHTBIT (honored by the + * slackware version of dircolors) are recognized but ignored. + * Global config options can be specified before TERM or COLORTERM entries + * below are TERM or COLORTERM entries, which can be glob patterns, which + * restrict following config to systems with matching environment variables. + * COLORTERM ?* +*/ + +static TERMS: &[&str] = &[ + "Eterm", + "ansi", + "*color*", + "con[0-9]*x[0-9]*", + "cons25", + "console", + "cygwin", + "*direct*", + "dtterm", + "gnome", + "hurd", + "jfbterm", + "konsole", + "kterm", + "linux", + "linux-c", + "mlterm", + "putty", + "rxvt*", + "screen*", + "st", + "terminator", + "tmux*", + "vt100", + "xterm*", +]; + +/* +# Below are the color init strings for the basic file types. +# One can use codes for 256 or more colors supported by modern terminals. +# The default color codes use the capabilities of an 8 color terminal +# with some additional attributes as per the following codes: +# Attribute codes: +# 00=none 01=bold 04=underscore 05=blink 07=reverse 08=concealed +# Text color codes: +# 30=black 31=red 32=green 33=yellow 34=blue 35=magenta 36=cyan 37=white +# Background color codes: +# 40=black 41=red 42=green 43=yellow 44=blue 45=magenta 46=cyan 47=white +#NORMAL 00 # no color code at all +#FILE 00 # regular file: use no color at all +*/ +static FILE_TYPES: &[(&str, &str)] = &[ + ("RESET", "0"), // reset to "normal" color + ("DIR", "01;34"), // directory + ("LINK", "01;36"), // symbolic link + ("MULTIHARDLINK", "00"), // regular file with more than one link + ("FIFO", "40;33"), // pipe + ("SOCK", "01;35"), // socket + ("DOOR", "01;35"), // door + ("BLK", "40;33;01"), // block device driver + ("CHR", "40;33;01"), // character device driver + ("ORPHAN", "40;31;01"), // symlink to nonexistent file, or non-stat'able file + ("MISSING", "00"), // ... and the files they point to + ("SETUID", "37;41"), // file that is setuid (u+s) + ("SETGID", "30;43"), // file that is setgid (g+s) + ("CAPABILITY", "00"), // file with capability + ("STICKY_OTHER_WRITABLE", "30;42"), // dir that is sticky and other-writable (+t,o+w) + ("OTHER_WRITABLE", "34;42"), // dir that is other-writable (o+w) and not sticky + ("STICKY", "37;44"), // dir with the sticky bit set (+t) and not other-writable + ("EXEC", "01;32"), // files with execute permission +]; + +/* +# List any file extensions like '.gz' or '.tar' that you would like ls +# to color below. Put the extension, a space, and the color init string. +# (and any comments you want to add after a '#') +*/ +static FILE_COLORS: &[(&str, &str)] = &[ + // Executables (Windows) + (".cmd", "01;32"), + (".exe", "01;32"), + (".com", "01;32"), + (".btm", "01;32"), + (".bat", "01;32"), + (".sh", "01;32"), + (".csh", "01;32"), + // Archives or compressed + (".tar", "01;31"), + (".tgz", "01;31"), + (".arc", "01;31"), + (".arj", "01;31"), + (".taz", "01;31"), + (".lha", "01;31"), + (".lz4", "01;31"), + (".lzh", "01;31"), + (".lzma", "01;31"), + (".tlz", "01;31"), + (".txz", "01;31"), + (".tzo", "01;31"), + (".t7z", "01;31"), + (".zip", "01;31"), + (".z", "01;31"), + (".dz", "01;31"), + (".gz", "01;31"), + (".lrz", "01;31"), + (".lz", "01;31"), + (".lzo", "01;31"), + (".xz", "01;31"), + (".zst", "01;31"), + (".tzst", "01;31"), + (".bz2", "01;31"), + (".bz", "01;31"), + (".tbz", "01;31"), + (".tbz2", "01;31"), + (".tz", "01;31"), + (".deb", "01;31"), + (".rpm", "01;31"), + (".jar", "01;31"), + (".war", "01;31"), + (".ear", "01;31"), + (".sar", "01;31"), + (".rar", "01;31"), + (".alz", "01;31"), + (".ace", "01;31"), + (".zoo", "01;31"), + (".cpio", "01;31"), + (".7z", "01;31"), + (".rz", "01;31"), + (".cab", "01;31"), + (".wim", "01;31"), + (".swm", "01;31"), + (".dwm", "01;31"), + (".esd", "01;31"), + // Image formats + (".avif", "01;35"), + (".jpg", "01;35"), + (".jpeg", "01;35"), + (".mjpg", "01;35"), + (".mjpeg", "01;35"), + (".gif", "01;35"), + (".bmp", "01;35"), + (".pbm", "01;35"), + (".pgm", "01;35"), + (".ppm", "01;35"), + (".tga", "01;35"), + (".xbm", "01;35"), + (".xpm", "01;35"), + (".tif", "01;35"), + (".tiff", "01;35"), + (".png", "01;35"), + (".svg", "01;35"), + (".svgz", "01;35"), + (".mng", "01;35"), + (".pcx", "01;35"), + (".mov", "01;35"), + (".mpg", "01;35"), + (".mpeg", "01;35"), + (".m2v", "01;35"), + (".mkv", "01;35"), + (".webm", "01;35"), + (".webp", "01;35"), + (".ogm", "01;35"), + (".mp4", "01;35"), + (".m4v", "01;35"), + (".mp4v", "01;35"), + (".vob", "01;35"), + (".qt", "01;35"), + (".nuv", "01;35"), + (".wmv", "01;35"), + (".asf", "01;35"), + (".rm", "01;35"), + (".rmvb", "01;35"), + (".flc", "01;35"), + (".avi", "01;35"), + (".fli", "01;35"), + (".flv", "01;35"), + (".gl", "01;35"), + (".dl", "01;35"), + (".xcf", "01;35"), + (".xwd", "01;35"), + (".yuv", "01;35"), + (".cgm", "01;35"), + (".emf", "01;35"), + (".ogv", "01;35"), + (".ogx", "01;35"), + // Audio formats + (".aac", "00;36"), + (".au", "00;36"), + (".flac", "00;36"), + (".m4a", "00;36"), + (".mid", "00;36"), + (".midi", "00;36"), + (".mka", "00;36"), + (".mp3", "00;36"), + (".mpc", "00;36"), + (".ogg", "00;36"), + (".ra", "00;36"), + (".wav", "00;36"), + (".oga", "00;36"), + (".opus", "00;36"), + (".spx", "00;36"), + (".xspf", "00;36"), + // Backup files + ("*~", "00;90"), + ("*#", "00;90"), + (".bak", "00;90"), + (".old", "00;90"), + (".orig", "00;90"), + (".part", "00;90"), + (".rej", "00;90"), + (".swp", "00;90"), + (".tmp", "00;90"), + (".dpkg-dist", "00;90"), + (".dpkg-old", "00;90"), + (".ucf-dist", "00;90"), + (".ucf-new", "00;90"), + (".ucf-old", "00;90"), + (".rpmnew", "00;90"), + (".rpmorig", "00;90"), + (".rpmsave", "00;90"), +]; From 0e8c171c80ab35f4d16a2613700648ec059ebaa5 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sat, 2 Dec 2023 11:00:55 +0100 Subject: [PATCH 0433/2851] dircolors: move the FILE_ATTRIBUTE_CODES datastructures and use it --- src/uu/dircolors/Cargo.toml | 2 +- src/uu/dircolors/src/dircolors.rs | 43 +-- src/uucore/Cargo.toml | 1 + src/uucore/src/lib/features/colors.rs | 458 +++++++++++++++----------- src/uucore/src/lib/lib.rs | 2 + 5 files changed, 273 insertions(+), 233 deletions(-) diff --git a/src/uu/dircolors/Cargo.toml b/src/uu/dircolors/Cargo.toml index 6099b5a8428..66ee792f839 100644 --- a/src/uu/dircolors/Cargo.toml +++ b/src/uu/dircolors/Cargo.toml @@ -16,7 +16,7 @@ path = "src/dircolors.rs" [dependencies] clap = { workspace = true } -uucore = { workspace = true } +uucore = { workspace = true, features = ["colors"] } [[bin]] name = "dircolors" diff --git a/src/uu/dircolors/src/dircolors.rs b/src/uu/dircolors/src/dircolors.rs index 2e3087d810b..58228ddeb57 100644 --- a/src/uu/dircolors/src/dircolors.rs +++ b/src/uu/dircolors/src/dircolors.rs @@ -12,6 +12,7 @@ use std::io::{BufRead, BufReader}; use std::path::Path; use clap::{crate_version, Arg, ArgAction, Command}; +use uucore::colors::FILE_ATTRIBUTE_CODES; use uucore::display::Quotable; use uucore::error::{UResult, USimpleError, UUsageError}; use uucore::{help_about, help_section, help_usage}; @@ -276,7 +277,6 @@ enum ParseState { Pass, } -use std::collections::HashMap; use uucore::{format_usage, parse_glob}; #[allow(clippy::cognitive_complexity)] @@ -294,45 +294,6 @@ where OutputFmt::Unknown => unreachable!(), } - let mut table: HashMap<&str, &str> = HashMap::with_capacity(48); - table.insert("normal", "no"); - table.insert("norm", "no"); - table.insert("file", "fi"); - table.insert("reset", "rs"); - table.insert("dir", "di"); - table.insert("lnk", "ln"); - table.insert("link", "ln"); - table.insert("symlink", "ln"); - table.insert("orphan", "or"); - table.insert("missing", "mi"); - table.insert("fifo", "pi"); - table.insert("pipe", "pi"); - table.insert("sock", "so"); - table.insert("blk", "bd"); - table.insert("block", "bd"); - table.insert("chr", "cd"); - table.insert("char", "cd"); - table.insert("door", "do"); - table.insert("exec", "ex"); - table.insert("left", "lc"); - table.insert("leftcode", "lc"); - table.insert("right", "rc"); - table.insert("rightcode", "rc"); - table.insert("end", "ec"); - table.insert("endcode", "ec"); - table.insert("suid", "su"); - table.insert("setuid", "su"); - table.insert("sgid", "sg"); - table.insert("setgid", "sg"); - table.insert("sticky", "st"); - table.insert("other_writable", "ow"); - table.insert("owr", "ow"); - table.insert("sticky_other_writable", "tw"); - table.insert("owt", "tw"); - table.insert("capability", "ca"); - table.insert("multihardlink", "mh"); - table.insert("clrtoeol", "cl"); - let term = env::var("TERM").unwrap_or_else(|_| "none".to_owned()); let term = term.as_str(); @@ -384,7 +345,7 @@ where } } else if lower == "options" || lower == "color" || lower == "eightbit" { // Slackware only. Ignore - } else if let Some(s) = table.get(lower.as_str()) { + } else if let Some(s) = FILE_ATTRIBUTE_CODES.get(lower.as_str()) { if *fmt == OutputFmt::Display { result.push_str(format!("\x1b[{val}m{s}\t{val}\x1b[0m\n").as_str()); } else { diff --git a/src/uucore/Cargo.toml b/src/uucore/Cargo.toml index b43445b4a92..44f8bb2d13f 100644 --- a/src/uucore/Cargo.toml +++ b/src/uucore/Cargo.toml @@ -72,6 +72,7 @@ windows-sys = { workspace = true, optional = true, default-features = false, fea default = [] # * non-default features backup-control = [] +colors = [] encoding = ["data-encoding", "data-encoding-macro", "z85", "thiserror"] entries = ["libc"] fs = ["dunce", "libc", "winapi-util", "windows-sys"] diff --git a/src/uucore/src/lib/features/colors.rs b/src/uucore/src/lib/features/colors.rs index 58b0b757084..69be16ba291 100644 --- a/src/uucore/src/lib/features/colors.rs +++ b/src/uucore/src/lib/features/colors.rs @@ -3,6 +3,9 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. +use once_cell::sync::Lazy; +use std::collections::HashMap; + /* The keywords COLOR, OPTIONS, and EIGHTBIT (honored by the * slackware version of dircolors) are recognized but ignored. * Global config options can be specified before TERM or COLORTERM entries @@ -11,33 +14,41 @@ * COLORTERM ?* */ -static TERMS: &[&str] = &[ - "Eterm", - "ansi", - "*color*", - "con[0-9]*x[0-9]*", - "cons25", - "console", - "cygwin", - "*direct*", - "dtterm", - "gnome", - "hurd", - "jfbterm", - "konsole", - "kterm", - "linux", - "linux-c", - "mlterm", - "putty", - "rxvt*", - "screen*", - "st", - "terminator", - "tmux*", - "vt100", - "xterm*", -]; +pub static TERMS: Lazy> = Lazy::new(|| { + let mut m = HashMap::new(); + [ + "Eterm", + "ansi", + "*color*", + "con[0-9]*x[0-9]*", + "cons25", + "console", + "cygwin", + "*direct*", + "dtterm", + "gnome", + "hurd", + "jfbterm", + "konsole", + "kterm", + "linux", + "linux-c", + "mlterm", + "putty", + "rxvt*", + "screen*", + "st", + "terminator", + "tmux*", + "vt100", + "xterm*", + ] + .iter() + .for_each(|&term| { + m.insert(term, ""); + }); + m +}); /* # Below are the color init strings for the basic file types. @@ -53,173 +64,238 @@ static TERMS: &[&str] = &[ #NORMAL 00 # no color code at all #FILE 00 # regular file: use no color at all */ -static FILE_TYPES: &[(&str, &str)] = &[ - ("RESET", "0"), // reset to "normal" color - ("DIR", "01;34"), // directory - ("LINK", "01;36"), // symbolic link - ("MULTIHARDLINK", "00"), // regular file with more than one link - ("FIFO", "40;33"), // pipe - ("SOCK", "01;35"), // socket - ("DOOR", "01;35"), // door - ("BLK", "40;33;01"), // block device driver - ("CHR", "40;33;01"), // character device driver - ("ORPHAN", "40;31;01"), // symlink to nonexistent file, or non-stat'able file - ("MISSING", "00"), // ... and the files they point to - ("SETUID", "37;41"), // file that is setuid (u+s) - ("SETGID", "30;43"), // file that is setgid (g+s) - ("CAPABILITY", "00"), // file with capability - ("STICKY_OTHER_WRITABLE", "30;42"), // dir that is sticky and other-writable (+t,o+w) - ("OTHER_WRITABLE", "34;42"), // dir that is other-writable (o+w) and not sticky - ("STICKY", "37;44"), // dir with the sticky bit set (+t) and not other-writable - ("EXEC", "01;32"), // files with execute permission -]; +// FILE_TYPES with Lazy initialization +pub static FILE_TYPES: Lazy> = Lazy::new(|| { + let mut m = HashMap::new(); + [ + ("RESET", "0"), // reset to "normal" color + ("DIR", "01;34"), // directory + ("LINK", "01;36"), // symbolic link + ("MULTIHARDLINK", "00"), // regular file with more than one link + ("FIFO", "40;33"), // pipe + ("SOCK", "01;35"), // socket + ("DOOR", "01;35"), // door + ("BLK", "40;33;01"), // block device driver + ("CHR", "40;33;01"), // character device driver + ("ORPHAN", "40;31;01"), // symlink to nonexistent file, or non-stat'able file + ("MISSING", "00"), // ... and the files they point to + ("SETUID", "37;41"), // file that is setuid (u+s) + ("SETGID", "30;43"), // file that is setgid (g+s) + ("CAPABILITY", "00"), // file with capability + ("STICKY_OTHER_WRITABLE", "30;42"), // dir that is sticky and other-writable (+t,o+w) + ("OTHER_WRITABLE", "34;42"), // dir that is other-writable (o+w) and not sticky + ("STICKY", "37;44"), // dir with the sticky bit set (+t) and not other-writable + ("EXEC", "01;32"), // files with execute permission + ] + .iter() + .for_each(|&(k, v)| { + m.insert(k, v); + }); + m +}); /* # List any file extensions like '.gz' or '.tar' that you would like ls # to color below. Put the extension, a space, and the color init string. # (and any comments you want to add after a '#') */ -static FILE_COLORS: &[(&str, &str)] = &[ - // Executables (Windows) - (".cmd", "01;32"), - (".exe", "01;32"), - (".com", "01;32"), - (".btm", "01;32"), - (".bat", "01;32"), - (".sh", "01;32"), - (".csh", "01;32"), - // Archives or compressed - (".tar", "01;31"), - (".tgz", "01;31"), - (".arc", "01;31"), - (".arj", "01;31"), - (".taz", "01;31"), - (".lha", "01;31"), - (".lz4", "01;31"), - (".lzh", "01;31"), - (".lzma", "01;31"), - (".tlz", "01;31"), - (".txz", "01;31"), - (".tzo", "01;31"), - (".t7z", "01;31"), - (".zip", "01;31"), - (".z", "01;31"), - (".dz", "01;31"), - (".gz", "01;31"), - (".lrz", "01;31"), - (".lz", "01;31"), - (".lzo", "01;31"), - (".xz", "01;31"), - (".zst", "01;31"), - (".tzst", "01;31"), - (".bz2", "01;31"), - (".bz", "01;31"), - (".tbz", "01;31"), - (".tbz2", "01;31"), - (".tz", "01;31"), - (".deb", "01;31"), - (".rpm", "01;31"), - (".jar", "01;31"), - (".war", "01;31"), - (".ear", "01;31"), - (".sar", "01;31"), - (".rar", "01;31"), - (".alz", "01;31"), - (".ace", "01;31"), - (".zoo", "01;31"), - (".cpio", "01;31"), - (".7z", "01;31"), - (".rz", "01;31"), - (".cab", "01;31"), - (".wim", "01;31"), - (".swm", "01;31"), - (".dwm", "01;31"), - (".esd", "01;31"), - // Image formats - (".avif", "01;35"), - (".jpg", "01;35"), - (".jpeg", "01;35"), - (".mjpg", "01;35"), - (".mjpeg", "01;35"), - (".gif", "01;35"), - (".bmp", "01;35"), - (".pbm", "01;35"), - (".pgm", "01;35"), - (".ppm", "01;35"), - (".tga", "01;35"), - (".xbm", "01;35"), - (".xpm", "01;35"), - (".tif", "01;35"), - (".tiff", "01;35"), - (".png", "01;35"), - (".svg", "01;35"), - (".svgz", "01;35"), - (".mng", "01;35"), - (".pcx", "01;35"), - (".mov", "01;35"), - (".mpg", "01;35"), - (".mpeg", "01;35"), - (".m2v", "01;35"), - (".mkv", "01;35"), - (".webm", "01;35"), - (".webp", "01;35"), - (".ogm", "01;35"), - (".mp4", "01;35"), - (".m4v", "01;35"), - (".mp4v", "01;35"), - (".vob", "01;35"), - (".qt", "01;35"), - (".nuv", "01;35"), - (".wmv", "01;35"), - (".asf", "01;35"), - (".rm", "01;35"), - (".rmvb", "01;35"), - (".flc", "01;35"), - (".avi", "01;35"), - (".fli", "01;35"), - (".flv", "01;35"), - (".gl", "01;35"), - (".dl", "01;35"), - (".xcf", "01;35"), - (".xwd", "01;35"), - (".yuv", "01;35"), - (".cgm", "01;35"), - (".emf", "01;35"), - (".ogv", "01;35"), - (".ogx", "01;35"), - // Audio formats - (".aac", "00;36"), - (".au", "00;36"), - (".flac", "00;36"), - (".m4a", "00;36"), - (".mid", "00;36"), - (".midi", "00;36"), - (".mka", "00;36"), - (".mp3", "00;36"), - (".mpc", "00;36"), - (".ogg", "00;36"), - (".ra", "00;36"), - (".wav", "00;36"), - (".oga", "00;36"), - (".opus", "00;36"), - (".spx", "00;36"), - (".xspf", "00;36"), - // Backup files - ("*~", "00;90"), - ("*#", "00;90"), - (".bak", "00;90"), - (".old", "00;90"), - (".orig", "00;90"), - (".part", "00;90"), - (".rej", "00;90"), - (".swp", "00;90"), - (".tmp", "00;90"), - (".dpkg-dist", "00;90"), - (".dpkg-old", "00;90"), - (".ucf-dist", "00;90"), - (".ucf-new", "00;90"), - (".ucf-old", "00;90"), - (".rpmnew", "00;90"), - (".rpmorig", "00;90"), - (".rpmsave", "00;90"), -]; +pub static FILE_COLORS: Lazy> = Lazy::new(|| { + let mut m = HashMap::new(); + [ + // Executables (Windows) + (".cmd", "01;32"), + (".exe", "01;32"), + (".com", "01;32"), + (".btm", "01;32"), + (".bat", "01;32"), + (".sh", "01;32"), + (".csh", "01;32"), + // Archives or compressed + (".tar", "01;31"), + (".tgz", "01;31"), + (".arc", "01;31"), + (".arj", "01;31"), + (".taz", "01;31"), + (".lha", "01;31"), + (".lz4", "01;31"), + (".lzh", "01;31"), + (".lzma", "01;31"), + (".tlz", "01;31"), + (".txz", "01;31"), + (".tzo", "01;31"), + (".t7z", "01;31"), + (".zip", "01;31"), + (".z", "01;31"), + (".dz", "01;31"), + (".gz", "01;31"), + (".lrz", "01;31"), + (".lz", "01;31"), + (".lzo", "01;31"), + (".xz", "01;31"), + (".zst", "01;31"), + (".tzst", "01;31"), + (".bz2", "01;31"), + (".bz", "01;31"), + (".tbz", "01;31"), + (".tbz2", "01;31"), + (".tz", "01;31"), + (".deb", "01;31"), + (".rpm", "01;31"), + (".jar", "01;31"), + (".war", "01;31"), + (".ear", "01;31"), + (".sar", "01;31"), + (".rar", "01;31"), + (".alz", "01;31"), + (".ace", "01;31"), + (".zoo", "01;31"), + (".cpio", "01;31"), + (".7z", "01;31"), + (".rz", "01;31"), + (".cab", "01;31"), + (".wim", "01;31"), + (".swm", "01;31"), + (".dwm", "01;31"), + (".esd", "01;31"), + // Image formats + (".avif", "01;35"), + (".jpg", "01;35"), + (".jpeg", "01;35"), + (".mjpg", "01;35"), + (".mjpeg", "01;35"), + (".gif", "01;35"), + (".bmp", "01;35"), + (".pbm", "01;35"), + (".pgm", "01;35"), + (".ppm", "01;35"), + (".tga", "01;35"), + (".xbm", "01;35"), + (".xpm", "01;35"), + (".tif", "01;35"), + (".tiff", "01;35"), + (".png", "01;35"), + (".svg", "01;35"), + (".svgz", "01;35"), + (".mng", "01;35"), + (".pcx", "01;35"), + (".mov", "01;35"), + (".mpg", "01;35"), + (".mpeg", "01;35"), + (".m2v", "01;35"), + (".mkv", "01;35"), + (".webm", "01;35"), + (".webp", "01;35"), + (".ogm", "01;35"), + (".mp4", "01;35"), + (".m4v", "01;35"), + (".mp4v", "01;35"), + (".vob", "01;35"), + (".qt", "01;35"), + (".nuv", "01;35"), + (".wmv", "01;35"), + (".asf", "01;35"), + (".rm", "01;35"), + (".rmvb", "01;35"), + (".flc", "01;35"), + (".avi", "01;35"), + (".fli", "01;35"), + (".flv", "01;35"), + (".gl", "01;35"), + (".dl", "01;35"), + (".xcf", "01;35"), + (".xwd", "01;35"), + (".yuv", "01;35"), + (".cgm", "01;35"), + (".emf", "01;35"), + (".ogv", "01;35"), + (".ogx", "01;35"), + // Audio formats + (".aac", "00;36"), + (".au", "00;36"), + (".flac", "00;36"), + (".m4a", "00;36"), + (".mid", "00;36"), + (".midi", "00;36"), + (".mka", "00;36"), + (".mp3", "00;36"), + (".mpc", "00;36"), + (".ogg", "00;36"), + (".ra", "00;36"), + (".wav", "00;36"), + (".oga", "00;36"), + (".opus", "00;36"), + (".spx", "00;36"), + (".xspf", "00;36"), + // Backup files + ("*~", "00;90"), + ("*#", "00;90"), + (".bak", "00;90"), + (".old", "00;90"), + (".orig", "00;90"), + (".part", "00;90"), + (".rej", "00;90"), + (".swp", "00;90"), + (".tmp", "00;90"), + (".dpkg-dist", "00;90"), + (".dpkg-old", "00;90"), + (".ucf-dist", "00;90"), + (".ucf-new", "00;90"), + (".ucf-old", "00;90"), + (".rpmnew", "00;90"), + (".rpmorig", "00;90"), + (".rpmsave", "00;90"), + ] + .iter() + .for_each(|&(k, v)| { + m.insert(k, v); + }); + m +}); + +pub static FILE_ATTRIBUTE_CODES: Lazy> = Lazy::new(|| { + let mut m = HashMap::new(); + [ + ("normal", "no"), + ("norm", "no"), + ("file", "fi"), + ("reset", "rs"), + ("dir", "di"), + ("lnk", "ln"), + ("link", "ln"), + ("symlink", "ln"), + ("orphan", "or"), + ("missing", "mi"), + ("fifo", "pi"), + ("pipe", "pi"), + ("sock", "so"), + ("blk", "bd"), + ("block", "bd"), + ("chr", "cd"), + ("char", "cd"), + ("door", "do"), + ("exec", "ex"), + ("left", "lc"), + ("leftcode", "lc"), + ("right", "rc"), + ("rightcode", "rc"), + ("end", "ec"), + ("endcode", "ec"), + ("suid", "su"), + ("setuid", "su"), + ("sgid", "sg"), + ("setgid", "sg"), + ("sticky", "st"), + ("other_writable", "ow"), + ("owr", "ow"), + ("sticky_other_writable", "tw"), + ("owt", "tw"), + ("capability", "ca"), + ("multihardlink", "mh"), + ("clrtoeol", "cl"), + ] + .iter() + .for_each(|&(k, v)| { + m.insert(k, v); + }); + m +}); diff --git a/src/uucore/src/lib/lib.rs b/src/uucore/src/lib/lib.rs index af8668ef02f..426b4216ca2 100644 --- a/src/uucore/src/lib/lib.rs +++ b/src/uucore/src/lib/lib.rs @@ -35,6 +35,8 @@ pub use crate::parser::shortcut_value_parser; // * feature-gated modules #[cfg(feature = "backup-control")] pub use crate::features::backup_control; +#[cfg(feature = "colors")] +pub use crate::features::colors; #[cfg(feature = "encoding")] pub use crate::features::encoding; #[cfg(feature = "format")] From 5d19f79cd0b791bf28c9c8d9d8f4fd61817f6026 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sat, 2 Dec 2023 12:57:38 +0100 Subject: [PATCH 0434/2851] dircolors should use the datastructures when printing --- src/uu/dircolors/src/dircolors.rs | 119 ++++++++++++++++++++++---- src/uucore/src/lib/features/colors.rs | 89 +++++++------------ 2 files changed, 134 insertions(+), 74 deletions(-) diff --git a/src/uu/dircolors/src/dircolors.rs b/src/uu/dircolors/src/dircolors.rs index 58228ddeb57..43e35c3d2a9 100644 --- a/src/uu/dircolors/src/dircolors.rs +++ b/src/uu/dircolors/src/dircolors.rs @@ -12,7 +12,7 @@ use std::io::{BufRead, BufReader}; use std::path::Path; use clap::{crate_version, Arg, ArgAction, Command}; -use uucore::colors::FILE_ATTRIBUTE_CODES; +use uucore::colors::{FILE_ATTRIBUTE_CODES, FILE_COLORS, FILE_TYPES}; use uucore::display::Quotable; use uucore::error::{UResult, USimpleError, UUsageError}; use uucore::{help_about, help_section, help_usage}; @@ -58,6 +58,89 @@ pub fn guess_syntax() -> OutputFmt { } } +fn get_colors_format_strings(fmt: &OutputFmt) -> (String, String) { + let prefix = match fmt { + OutputFmt::Shell => "LS_COLORS='".to_string(), + OutputFmt::CShell => "setenv LS_COLORS '".to_string(), + OutputFmt::Display => String::new(), + OutputFmt::Unknown => unreachable!(), + }; + + let suffix = match fmt { + OutputFmt::Shell => "';\nexport LS_COLORS".to_string(), + OutputFmt::CShell => "'".to_string(), + OutputFmt::Display => String::new(), + OutputFmt::Unknown => unreachable!(), + }; + + (prefix, suffix) +} + +pub fn generate_type_output(fmt: &OutputFmt) -> String { + match fmt { + OutputFmt::Display => FILE_TYPES + .iter() + .map(|&(_, key, val)| format!("\x1b[{}m{}\t{}\x1b[0m", val, key, val)) + .collect::>() + .join("\n"), + _ => { + // Existing logic for other formats + FILE_TYPES + .iter() + .map(|&(_, v1, v2)| format!("{}={}", v1, v2)) + .collect::>() + .join(":") + } + } +} + +enum ExtensionFormat { + StarDot, // Format as ".*ext" + Dot, // Format as ".ext" + NoDot, // Format as "ext" +} + +fn generate_ls_colors(fmt: &OutputFmt, format: ExtensionFormat, sep: &str) -> String { + match fmt { + OutputFmt::Display => { + let mut display_parts = vec![]; + let type_output = generate_type_output(fmt); + display_parts.push(type_output); + for &(extension, code) in FILE_COLORS.iter() { + display_parts.push(format!("\x1b[{}m*{}\t{}\x1b[0m", code, extension, code)); + } + display_parts.join("\n") + } + _ => { + // existing logic for other formats + let mut parts = vec![]; + for &(extension, code) in FILE_COLORS.iter() { + let formatted_extension = match format { + ExtensionFormat::StarDot => format!("*{}", extension), + ExtensionFormat::Dot => extension.to_string(), + ExtensionFormat::NoDot => { + if extension.starts_with('.') { + extension[1..].to_string() + } else { + extension.to_string() + } + } + }; + parts.push(format!("{}={}", formatted_extension, code)); + } + let (prefix, suffix) = get_colors_format_strings(&fmt); + let ls_colors = parts.join(sep); + format!( + "{}{}:{}:{}", + prefix, + generate_type_output(&fmt), + ls_colors, + suffix + ) + } + } +} + #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { let args = args.collect_ignore(); @@ -126,7 +209,12 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let result; if files.is_empty() { - result = parse(INTERNAL_DB.lines(), &out_format, ""); + println!( + "{}", + generate_ls_colors(&out_format, ExtensionFormat::StarDot, ":") + ); + + return Ok(()); } else if files.len() > 1 { return Err(UUsageError::new( 1, @@ -287,12 +375,9 @@ where { // 1790 > $(dircolors | wc -m) let mut result = String::with_capacity(1790); - match fmt { - OutputFmt::Shell => result.push_str("LS_COLORS='"), - OutputFmt::CShell => result.push_str("setenv LS_COLORS '"), - OutputFmt::Display => (), - OutputFmt::Unknown => unreachable!(), - } + let (prefix, suffix) = get_colors_format_strings(&fmt); + + result.push_str(&prefix); let term = env::var("TERM").unwrap_or_else(|_| "none".to_owned()); let term = term.as_str(); @@ -331,6 +416,7 @@ where state = ParseState::Continue; } if state != ParseState::Pass { + let search_key = lower.as_str(); if key.starts_with('.') { if *fmt == OutputFmt::Display { result.push_str(format!("\x1b[{val}m*{key}\t{val}\x1b[0m\n").as_str()); @@ -345,7 +431,10 @@ where } } else if lower == "options" || lower == "color" || lower == "eightbit" { // Slackware only. Ignore - } else if let Some(s) = FILE_ATTRIBUTE_CODES.get(lower.as_str()) { + } else if let Some((_, s)) = FILE_ATTRIBUTE_CODES + .iter() + .find(|&&(key, _)| key == search_key) + { if *fmt == OutputFmt::Display { result.push_str(format!("\x1b[{val}m{s}\t{val}\x1b[0m\n").as_str()); } else { @@ -363,15 +452,11 @@ where } } - match fmt { - OutputFmt::Shell => result.push_str("';\nexport LS_COLORS"), - OutputFmt::CShell => result.push('\''), - OutputFmt::Display => { - // remove latest "\n" - result.pop(); - } - OutputFmt::Unknown => unreachable!(), + if fmt == &OutputFmt::Display { + // remove latest "\n" + result.pop(); } + result.push_str(&suffix); Ok(result) } diff --git a/src/uucore/src/lib/features/colors.rs b/src/uucore/src/lib/features/colors.rs index 69be16ba291..96ca6d45672 100644 --- a/src/uucore/src/lib/features/colors.rs +++ b/src/uucore/src/lib/features/colors.rs @@ -4,19 +4,15 @@ // file that was distributed with this source code. use once_cell::sync::Lazy; -use std::collections::HashMap; /* The keywords COLOR, OPTIONS, and EIGHTBIT (honored by the * slackware version of dircolors) are recognized but ignored. * Global config options can be specified before TERM or COLORTERM entries * below are TERM or COLORTERM entries, which can be glob patterns, which * restrict following config to systems with matching environment variables. - * COLORTERM ?* */ - -pub static TERMS: Lazy> = Lazy::new(|| { - let mut m = HashMap::new(); - [ +pub static TERMS: Lazy> = Lazy::new(|| { + vec![ "Eterm", "ansi", "*color*", @@ -43,11 +39,6 @@ pub static TERMS: Lazy> = Lazy::new(|| { "vt100", "xterm*", ] - .iter() - .for_each(|&term| { - m.insert(term, ""); - }); - m }); /* @@ -64,34 +55,27 @@ pub static TERMS: Lazy> = Lazy::new(|| { #NORMAL 00 # no color code at all #FILE 00 # regular file: use no color at all */ -// FILE_TYPES with Lazy initialization -pub static FILE_TYPES: Lazy> = Lazy::new(|| { - let mut m = HashMap::new(); - [ - ("RESET", "0"), // reset to "normal" color - ("DIR", "01;34"), // directory - ("LINK", "01;36"), // symbolic link - ("MULTIHARDLINK", "00"), // regular file with more than one link - ("FIFO", "40;33"), // pipe - ("SOCK", "01;35"), // socket - ("DOOR", "01;35"), // door - ("BLK", "40;33;01"), // block device driver - ("CHR", "40;33;01"), // character device driver - ("ORPHAN", "40;31;01"), // symlink to nonexistent file, or non-stat'able file - ("MISSING", "00"), // ... and the files they point to - ("SETUID", "37;41"), // file that is setuid (u+s) - ("SETGID", "30;43"), // file that is setgid (g+s) - ("CAPABILITY", "00"), // file with capability - ("STICKY_OTHER_WRITABLE", "30;42"), // dir that is sticky and other-writable (+t,o+w) - ("OTHER_WRITABLE", "34;42"), // dir that is other-writable (o+w) and not sticky - ("STICKY", "37;44"), // dir with the sticky bit set (+t) and not other-writable - ("EXEC", "01;32"), // files with execute permission +pub static FILE_TYPES: Lazy> = Lazy::new(|| { + vec![ + ("RESET", "rs", "0"), // reset to "normal" color + ("DIR", "di", "01;34"), // directory + ("LINK", "ln", "01;36"), // symbolic link + ("MULTIHARDLINK", "mh", "00"), // regular file with more than one link + ("FIFO", "pi", "40;33"), // pipe + ("SOCK", "so", "01;35"), // socket + ("DOOR", "do", "01;35"), // door + ("BLK", "bd", "40;33;01"), // block device driver + ("CHR", "cd", "40;33;01"), // character device driver + ("ORPHAN", "or", "40;31;01"), // symlink to nonexistent file, or non-stat'able file + ("MISSING", "mi", "00"), // ... and the files they point to + ("SETUID", "su", "37;41"), // file that is setuid (u+s) + ("SETGID", "sg", "30;43"), // file that is setgid (g+s) + ("CAPABILITY", "ca", "00"), // file with capability + ("STICKY_OTHER_WRITABLE", "tw", "30;42"), // dir that is sticky and other-writable (+t,o+w) + ("OTHER_WRITABLE", "ow", "34;42"), // dir that is other-writable (o+w) and not sticky + ("STICKY", "st", "37;44"), // dir with the sticky bit set (+t) and not other-writable + ("EXEC", "ex", "01;32"), // files with execute permission ] - .iter() - .for_each(|&(k, v)| { - m.insert(k, v); - }); - m }); /* @@ -99,9 +83,9 @@ pub static FILE_TYPES: Lazy> = Lazy::new(|| { # to color below. Put the extension, a space, and the color init string. # (and any comments you want to add after a '#') */ -pub static FILE_COLORS: Lazy> = Lazy::new(|| { - let mut m = HashMap::new(); - [ +pub static FILE_COLORS: Lazy> = Lazy::new(|| { + vec![ + /* // Executables (Windows) (".cmd", "01;32"), (".exe", "01;32"), @@ -109,7 +93,7 @@ pub static FILE_COLORS: Lazy> = Lazy::new(|| { (".btm", "01;32"), (".bat", "01;32"), (".sh", "01;32"), - (".csh", "01;32"), + (".csh", "01;32"),*/ // Archives or compressed (".tar", "01;31"), (".tgz", "01;31"), @@ -207,6 +191,7 @@ pub static FILE_COLORS: Lazy> = Lazy::new(|| { (".yuv", "01;35"), (".cgm", "01;35"), (".emf", "01;35"), + // https://wiki.xiph.org/MIME_Types_and_File_Extensions (".ogv", "01;35"), (".ogx", "01;35"), // Audio formats @@ -222,13 +207,14 @@ pub static FILE_COLORS: Lazy> = Lazy::new(|| { (".ogg", "00;36"), (".ra", "00;36"), (".wav", "00;36"), + // https://wiki.xiph.org/MIME_Types_and_File_Extensions (".oga", "00;36"), (".opus", "00;36"), (".spx", "00;36"), (".xspf", "00;36"), // Backup files - ("*~", "00;90"), - ("*#", "00;90"), + ("~", "00;90"), + ("#", "00;90"), (".bak", "00;90"), (".old", "00;90"), (".orig", "00;90"), @@ -245,16 +231,10 @@ pub static FILE_COLORS: Lazy> = Lazy::new(|| { (".rpmorig", "00;90"), (".rpmsave", "00;90"), ] - .iter() - .for_each(|&(k, v)| { - m.insert(k, v); - }); - m }); -pub static FILE_ATTRIBUTE_CODES: Lazy> = Lazy::new(|| { - let mut m = HashMap::new(); - [ +pub static FILE_ATTRIBUTE_CODES: Lazy> = Lazy::new(|| { + vec![ ("normal", "no"), ("norm", "no"), ("file", "fi"), @@ -293,9 +273,4 @@ pub static FILE_ATTRIBUTE_CODES: Lazy> = Lazy::new(|| { ("multihardlink", "mh"), ("clrtoeol", "cl"), ] - .iter() - .for_each(|&(k, v)| { - m.insert(k, v); - }); - m }); From e4b875043429ac78f4ae4b2824caea3e8d180244 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sat, 2 Dec 2023 15:06:09 +0100 Subject: [PATCH 0435/2851] dircolors -p: generate it dynamically --- src/uu/dircolors/src/colors.rs | 225 --------------------- src/uu/dircolors/src/dircolors.rs | 65 +++++- tests/fixtures/dircolors/internal.expected | 60 ++---- 3 files changed, 75 insertions(+), 275 deletions(-) delete mode 100644 src/uu/dircolors/src/colors.rs diff --git a/src/uu/dircolors/src/colors.rs b/src/uu/dircolors/src/colors.rs deleted file mode 100644 index c0a981db89c..00000000000 --- a/src/uu/dircolors/src/colors.rs +++ /dev/null @@ -1,225 +0,0 @@ -// This file is part of the uutils coreutils package. -// -// For the full copyright and license information, please view the LICENSE -// file that was distributed with this source code. -// spell-checker:ignore (ToDO) EIGHTBIT ETERM MULTIHARDLINK cpio dtterm jfbterm konsole kterm mlterm rmvb rxvt stat'able svgz tmux webm xspf COLORTERM tzst avif tzst mjpg mjpeg webp dpkg rpmnew rpmorig rpmsave - -pub const INTERNAL_DB: &str = r#"# Configuration file for dircolors, a utility to help you set the -# LS_COLORS environment variable used by GNU ls with the --color option. -# Copyright (C) 1996-2022 Free Software Foundation, Inc. -# Copying and distribution of this file, with or without modification, -# are permitted provided the copyright notice and this notice are preserved. -# The keywords COLOR, OPTIONS, and EIGHTBIT (honored by the -# slackware version of dircolors) are recognized but ignored. -# Global config options can be specified before TERM or COLORTERM entries -# Below are TERM or COLORTERM entries, which can be glob patterns, which -# restrict following config to systems with matching environment variables. -COLORTERM ?* -TERM Eterm -TERM ansi -TERM *color* -TERM con[0-9]*x[0-9]* -TERM cons25 -TERM console -TERM cygwin -TERM *direct* -TERM dtterm -TERM gnome -TERM hurd -TERM jfbterm -TERM konsole -TERM kterm -TERM linux -TERM linux-c -TERM mlterm -TERM putty -TERM rxvt* -TERM screen* -TERM st -TERM terminator -TERM tmux* -TERM vt100 -TERM xterm* -# Below are the color init strings for the basic file types. -# One can use codes for 256 or more colors supported by modern terminals. -# The default color codes use the capabilities of an 8 color terminal -# with some additional attributes as per the following codes: -# Attribute codes: -# 00=none 01=bold 04=underscore 05=blink 07=reverse 08=concealed -# Text color codes: -# 30=black 31=red 32=green 33=yellow 34=blue 35=magenta 36=cyan 37=white -# Background color codes: -# 40=black 41=red 42=green 43=yellow 44=blue 45=magenta 46=cyan 47=white -#NORMAL 00 # no color code at all -#FILE 00 # regular file: use no color at all -RESET 0 # reset to "normal" color -DIR 01;34 # directory -LINK 01;36 # symbolic link. (If you set this to 'target' instead of a - # numerical value, the color is as for the file pointed to.) -MULTIHARDLINK 00 # regular file with more than one link -FIFO 40;33 # pipe -SOCK 01;35 # socket -DOOR 01;35 # door -BLK 40;33;01 # block device driver -CHR 40;33;01 # character device driver -ORPHAN 40;31;01 # symlink to nonexistent file, or non-stat'able file ... -MISSING 00 # ... and the files they point to -SETUID 37;41 # file that is setuid (u+s) -SETGID 30;43 # file that is setgid (g+s) -CAPABILITY 00 # file with capability (very expensive to lookup) -STICKY_OTHER_WRITABLE 30;42 # dir that is sticky and other-writable (+t,o+w) -OTHER_WRITABLE 34;42 # dir that is other-writable (o+w) and not sticky -STICKY 37;44 # dir with the sticky bit set (+t) and not other-writable -# This is for files with execute permission: -EXEC 01;32 -# List any file extensions like '.gz' or '.tar' that you would like ls -# to color below. Put the extension, a space, and the color init string. -# (and any comments you want to add after a '#') -# If you use DOS-style suffixes, you may want to uncomment the following: -#.cmd 01;32 # executables (bright green) -#.exe 01;32 -#.com 01;32 -#.btm 01;32 -#.bat 01;32 -# Or if you want to color scripts even if they do not have the -# executable bit actually set. -#.sh 01;32 -#.csh 01;32 - # archives or compressed (bright red) -.tar 01;31 -.tgz 01;31 -.arc 01;31 -.arj 01;31 -.taz 01;31 -.lha 01;31 -.lz4 01;31 -.lzh 01;31 -.lzma 01;31 -.tlz 01;31 -.txz 01;31 -.tzo 01;31 -.t7z 01;31 -.zip 01;31 -.z 01;31 -.dz 01;31 -.gz 01;31 -.lrz 01;31 -.lz 01;31 -.lzo 01;31 -.xz 01;31 -.zst 01;31 -.tzst 01;31 -.bz2 01;31 -.bz 01;31 -.tbz 01;31 -.tbz2 01;31 -.tz 01;31 -.deb 01;31 -.rpm 01;31 -.jar 01;31 -.war 01;31 -.ear 01;31 -.sar 01;31 -.rar 01;31 -.alz 01;31 -.ace 01;31 -.zoo 01;31 -.cpio 01;31 -.7z 01;31 -.rz 01;31 -.cab 01;31 -.wim 01;31 -.swm 01;31 -.dwm 01;31 -.esd 01;31 -# image formats -.avif 01;35 -.jpg 01;35 -.jpeg 01;35 -.mjpg 01;35 -.mjpeg 01;35 -.gif 01;35 -.bmp 01;35 -.pbm 01;35 -.pgm 01;35 -.ppm 01;35 -.tga 01;35 -.xbm 01;35 -.xpm 01;35 -.tif 01;35 -.tiff 01;35 -.png 01;35 -.svg 01;35 -.svgz 01;35 -.mng 01;35 -.pcx 01;35 -.mov 01;35 -.mpg 01;35 -.mpeg 01;35 -.m2v 01;35 -.mkv 01;35 -.webm 01;35 -.webp 01;35 -.ogm 01;35 -.mp4 01;35 -.m4v 01;35 -.mp4v 01;35 -.vob 01;35 -.qt 01;35 -.nuv 01;35 -.wmv 01;35 -.asf 01;35 -.rm 01;35 -.rmvb 01;35 -.flc 01;35 -.avi 01;35 -.fli 01;35 -.flv 01;35 -.gl 01;35 -.dl 01;35 -.xcf 01;35 -.xwd 01;35 -.yuv 01;35 -.cgm 01;35 -.emf 01;35 -# https://wiki.xiph.org/MIME_Types_and_File_Extensions -.ogv 01;35 -.ogx 01;35 -# audio formats -.aac 00;36 -.au 00;36 -.flac 00;36 -.m4a 00;36 -.mid 00;36 -.midi 00;36 -.mka 00;36 -.mp3 00;36 -.mpc 00;36 -.ogg 00;36 -.ra 00;36 -.wav 00;36 -# https://wiki.xiph.org/MIME_Types_and_File_Extensions -.oga 00;36 -.opus 00;36 -.spx 00;36 -.xspf 00;36 -# backup files -*~ 00;90 -*# 00;90 -.bak 00;90 -.old 00;90 -.orig 00;90 -.part 00;90 -.rej 00;90 -.swp 00;90 -.tmp 00;90 -.dpkg-dist 00;90 -.dpkg-old 00;90 -.ucf-dist 00;90 -.ucf-new 00;90 -.ucf-old 00;90 -.rpmnew 00;90 -.rpmorig 00;90 -.rpmsave 00;90 -# Subsequent TERM or COLORTERM entries, can be used to add / override -# config specific to those matching environment variables."#; diff --git a/src/uu/dircolors/src/dircolors.rs b/src/uu/dircolors/src/dircolors.rs index 43e35c3d2a9..338bf83e514 100644 --- a/src/uu/dircolors/src/dircolors.rs +++ b/src/uu/dircolors/src/dircolors.rs @@ -10,9 +10,10 @@ use std::env; use std::fs::File; use std::io::{BufRead, BufReader}; use std::path::Path; +use std::fmt::Write; use clap::{crate_version, Arg, ArgAction, Command}; -use uucore::colors::{FILE_ATTRIBUTE_CODES, FILE_COLORS, FILE_TYPES}; +use uucore::colors::{FILE_ATTRIBUTE_CODES, FILE_COLORS, FILE_TYPES, TERMS}; use uucore::display::Quotable; use uucore::error::{UResult, USimpleError, UUsageError}; use uucore::{help_about, help_section, help_usage}; @@ -29,9 +30,6 @@ const USAGE: &str = help_usage!("dircolors.md"); const ABOUT: &str = help_about!("dircolors.md"); const AFTER_HELP: &str = help_section!("after help", "dircolors.md"); -mod colors; -use self::colors::INTERNAL_DB; - #[derive(PartialEq, Eq, Debug)] pub enum OutputFmt { Shell, @@ -181,7 +179,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { ), )); } - println!("{INTERNAL_DB}"); + + println!("{}", generate_dircolors_config()); return Ok(()); } @@ -222,6 +221,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { )); } else if files[0].eq("-") { let fin = BufReader::new(std::io::stdin()); + // For example, for echo "owt 40;33"|dircolors -b - result = parse(fin.lines().map_while(Result::ok), &out_format, files[0]); } else { let path = Path::new(files[0]); @@ -368,7 +368,7 @@ enum ParseState { use uucore::{format_usage, parse_glob}; #[allow(clippy::cognitive_complexity)] -fn parse(lines: T, fmt: &OutputFmt, fp: &str) -> Result +fn parse(user_input: T, fmt: &OutputFmt, fp: &str) -> Result where T: IntoIterator, T::Item: Borrow, @@ -384,7 +384,7 @@ where let mut state = ParseState::Global; - for (num, line) in lines.into_iter().enumerate() { + for (num, line) in user_input.into_iter().enumerate() { let num = num + 1; let line = line.borrow().purify(); if line.is_empty() { @@ -396,13 +396,12 @@ where let (key, val) = line.split_two(); if val.is_empty() { return Err(format!( - "{}:{}: invalid line; missing second token", + "{}:{}: invalid line; missing second token", fp.maybe_quote(), num )); } let lower = key.to_lowercase(); - if lower == "term" || lower == "colorterm" { if term.fnmatch(val) { state = ParseState::Matched; @@ -417,6 +416,7 @@ where } if state != ParseState::Pass { let search_key = lower.as_str(); + if key.starts_with('.') { if *fmt == OutputFmt::Display { result.push_str(format!("\x1b[{val}m*{key}\t{val}\x1b[0m\n").as_str()); @@ -482,6 +482,53 @@ fn escape(s: &str) -> String { result } + +pub fn generate_dircolors_config() -> String { + let mut config = String::new(); + + // Adding the complete header comments as in the original file + writeln!(config, "# Configuration file for dircolors, a utility to help you set the").unwrap(); + writeln!(config, "# LS_COLORS environment variable used by GNU ls with the --color option.").unwrap(); + writeln!(config, "# The keywords COLOR, OPTIONS, and EIGHTBIT (honored by the").unwrap(); + writeln!(config, "# slackware version of dircolors) are recognized but ignored.").unwrap(); + writeln!(config, "# Global config options can be specified before TERM or COLORTERM entries").unwrap(); + writeln!(config, "# Below are TERM or COLORTERM entries, which can be glob patterns, which").unwrap(); + writeln!(config, "# restrict following config to systems with matching environment variables.").unwrap(); + writeln!(config, "COLORTERM ?*").unwrap(); + for term in TERMS.iter() { + writeln!(config, "TERM {}", term).unwrap(); + } + + // Adding file types and their color codes with header + writeln!(config, "# Below are the color init strings for the basic file types.").unwrap(); + writeln!(config, "# One can use codes for 256 or more colors supported by modern terminals.").unwrap(); + writeln!(config, "# The default color codes use the capabilities of an 8 color terminal").unwrap(); + writeln!(config, "# with some additional attributes as per the following codes:").unwrap(); + writeln!(config, "# Attribute codes:").unwrap(); + writeln!(config, "# 00=none 01=bold 04=underscore 05=blink 07=reverse 08=concealed").unwrap(); + writeln!(config, "# Text color codes:").unwrap(); + writeln!(config, "# 30=black 31=red 32=green 33=yellow 34=blue 35=magenta 36=cyan 37=white").unwrap(); + writeln!(config, "# Background color codes:").unwrap(); + writeln!(config, "# 40=black 41=red 42=green 43=yellow 44=blue 45=magenta 46=cyan 47=white").unwrap(); + writeln!(config, "#NORMAL 00 # no color code at all").unwrap(); + writeln!(config, "#FILE 00 # regular file: use no color at all").unwrap(); + + for (name, _, code) in FILE_TYPES.iter() { + writeln!(config, "{} {}", name, code).unwrap(); + } + + writeln!(config, "# List any file extensions like '.gz' or '.tar' that you would like ls").unwrap(); + writeln!(config, "# to color below. Put the extension, a space, and the color init string.").unwrap(); + + for (ext, color) in FILE_COLORS.iter() { + writeln!(config, "{} {}", ext, color).unwrap(); + } + writeln!(config, "# Subsequent TERM or COLORTERM entries, can be used to add / override").unwrap(); + write!(config, "# config specific to those matching environment variables.").unwrap(); + + config +} + #[cfg(test)] mod tests { use super::escape; diff --git a/tests/fixtures/dircolors/internal.expected b/tests/fixtures/dircolors/internal.expected index 7bc91ef470c..933e70bc470 100644 --- a/tests/fixtures/dircolors/internal.expected +++ b/tests/fixtures/dircolors/internal.expected @@ -1,8 +1,5 @@ # Configuration file for dircolors, a utility to help you set the # LS_COLORS environment variable used by GNU ls with the --color option. -# Copyright (C) 1996-2022 Free Software Foundation, Inc. -# Copying and distribution of this file, with or without modification, -# are permitted provided the copyright notice and this notice are preserved. # The keywords COLOR, OPTIONS, and EIGHTBIT (honored by the # slackware version of dircolors) are recognized but ignored. # Global config options can be specified before TERM or COLORTERM entries @@ -46,40 +43,26 @@ TERM xterm* # 40=black 41=red 42=green 43=yellow 44=blue 45=magenta 46=cyan 47=white #NORMAL 00 # no color code at all #FILE 00 # regular file: use no color at all -RESET 0 # reset to "normal" color -DIR 01;34 # directory -LINK 01;36 # symbolic link. (If you set this to 'target' instead of a - # numerical value, the color is as for the file pointed to.) -MULTIHARDLINK 00 # regular file with more than one link -FIFO 40;33 # pipe -SOCK 01;35 # socket -DOOR 01;35 # door -BLK 40;33;01 # block device driver -CHR 40;33;01 # character device driver -ORPHAN 40;31;01 # symlink to nonexistent file, or non-stat'able file ... -MISSING 00 # ... and the files they point to -SETUID 37;41 # file that is setuid (u+s) -SETGID 30;43 # file that is setgid (g+s) -CAPABILITY 00 # file with capability (very expensive to lookup) -STICKY_OTHER_WRITABLE 30;42 # dir that is sticky and other-writable (+t,o+w) -OTHER_WRITABLE 34;42 # dir that is other-writable (o+w) and not sticky -STICKY 37;44 # dir with the sticky bit set (+t) and not other-writable -# This is for files with execute permission: +RESET 0 +DIR 01;34 +LINK 01;36 +MULTIHARDLINK 00 +FIFO 40;33 +SOCK 01;35 +DOOR 01;35 +BLK 40;33;01 +CHR 40;33;01 +ORPHAN 40;31;01 +MISSING 00 +SETUID 37;41 +SETGID 30;43 +CAPABILITY 00 +STICKY_OTHER_WRITABLE 30;42 +OTHER_WRITABLE 34;42 +STICKY 37;44 EXEC 01;32 # List any file extensions like '.gz' or '.tar' that you would like ls # to color below. Put the extension, a space, and the color init string. -# (and any comments you want to add after a '#') -# If you use DOS-style suffixes, you may want to uncomment the following: -#.cmd 01;32 # executables (bright green) -#.exe 01;32 -#.com 01;32 -#.btm 01;32 -#.bat 01;32 -# Or if you want to color scripts even if they do not have the -# executable bit actually set. -#.sh 01;32 -#.csh 01;32 - # archives or compressed (bright red) .tar 01;31 .tgz 01;31 .arc 01;31 @@ -126,7 +109,6 @@ EXEC 01;32 .swm 01;31 .dwm 01;31 .esd 01;31 -# image formats .avif 01;35 .jpg 01;35 .jpeg 01;35 @@ -176,10 +158,8 @@ EXEC 01;32 .yuv 01;35 .cgm 01;35 .emf 01;35 -# https://wiki.xiph.org/MIME_Types_and_File_Extensions .ogv 01;35 .ogx 01;35 -# audio formats .aac 00;36 .au 00;36 .flac 00;36 @@ -192,14 +172,12 @@ EXEC 01;32 .ogg 00;36 .ra 00;36 .wav 00;36 -# https://wiki.xiph.org/MIME_Types_and_File_Extensions .oga 00;36 .opus 00;36 .spx 00;36 .xspf 00;36 -# backup files -*~ 00;90 -*# 00;90 +~ 00;90 +# 00;90 .bak 00;90 .old 00;90 .orig 00;90 From 1a4ca7e65dc6991f8584c6f03d493bace427ff2d Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sat, 2 Dec 2023 15:18:20 +0100 Subject: [PATCH 0436/2851] fix clippy warnings --- src/uu/dircolors/src/dircolors.rs | 143 +++++++++++++++++--------- src/uucore/src/lib/features/colors.rs | 2 +- 2 files changed, 98 insertions(+), 47 deletions(-) diff --git a/src/uu/dircolors/src/dircolors.rs b/src/uu/dircolors/src/dircolors.rs index 338bf83e514..28d74775db8 100644 --- a/src/uu/dircolors/src/dircolors.rs +++ b/src/uu/dircolors/src/dircolors.rs @@ -7,10 +7,10 @@ use std::borrow::Borrow; use std::env; +use std::fmt::Write; use std::fs::File; use std::io::{BufRead, BufReader}; use std::path::Path; -use std::fmt::Write; use clap::{crate_version, Arg, ArgAction, Command}; use uucore::colors::{FILE_ATTRIBUTE_CODES, FILE_COLORS, FILE_TYPES, TERMS}; @@ -92,13 +92,7 @@ pub fn generate_type_output(fmt: &OutputFmt) -> String { } } -enum ExtensionFormat { - StarDot, // Format as ".*ext" - Dot, // Format as ".ext" - NoDot, // Format as "ext" -} - -fn generate_ls_colors(fmt: &OutputFmt, format: ExtensionFormat, sep: &str) -> String { +fn generate_ls_colors(fmt: &OutputFmt, sep: &str) -> String { match fmt { OutputFmt::Display => { let mut display_parts = vec![]; @@ -113,25 +107,15 @@ fn generate_ls_colors(fmt: &OutputFmt, format: ExtensionFormat, sep: &str) -> St // existing logic for other formats let mut parts = vec![]; for &(extension, code) in FILE_COLORS.iter() { - let formatted_extension = match format { - ExtensionFormat::StarDot => format!("*{}", extension), - ExtensionFormat::Dot => extension.to_string(), - ExtensionFormat::NoDot => { - if extension.starts_with('.') { - extension[1..].to_string() - } else { - extension.to_string() - } - } - }; + let formatted_extension = format!("*{}", extension); parts.push(format!("{}={}", formatted_extension, code)); } - let (prefix, suffix) = get_colors_format_strings(&fmt); + let (prefix, suffix) = get_colors_format_strings(fmt); let ls_colors = parts.join(sep); format!( "{}{}:{}:{}", prefix, - generate_type_output(&fmt), + generate_type_output(fmt), ls_colors, suffix ) @@ -208,10 +192,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let result; if files.is_empty() { - println!( - "{}", - generate_ls_colors(&out_format, ExtensionFormat::StarDot, ":") - ); + println!("{}", generate_ls_colors(&out_format, ":")); return Ok(()); } else if files.len() > 1 { @@ -373,9 +354,8 @@ where T: IntoIterator, T::Item: Borrow, { - // 1790 > $(dircolors | wc -m) let mut result = String::with_capacity(1790); - let (prefix, suffix) = get_colors_format_strings(&fmt); + let (prefix, suffix) = get_colors_format_strings(fmt); result.push_str(&prefix); @@ -482,34 +462,89 @@ fn escape(s: &str) -> String { result } - pub fn generate_dircolors_config() -> String { let mut config = String::new(); // Adding the complete header comments as in the original file - writeln!(config, "# Configuration file for dircolors, a utility to help you set the").unwrap(); - writeln!(config, "# LS_COLORS environment variable used by GNU ls with the --color option.").unwrap(); - writeln!(config, "# The keywords COLOR, OPTIONS, and EIGHTBIT (honored by the").unwrap(); - writeln!(config, "# slackware version of dircolors) are recognized but ignored.").unwrap(); - writeln!(config, "# Global config options can be specified before TERM or COLORTERM entries").unwrap(); - writeln!(config, "# Below are TERM or COLORTERM entries, which can be glob patterns, which").unwrap(); - writeln!(config, "# restrict following config to systems with matching environment variables.").unwrap(); + writeln!( + config, + "# Configuration file for dircolors, a utility to help you set the" + ) + .unwrap(); + writeln!( + config, + "# LS_COLORS environment variable used by GNU ls with the --color option." + ) + .unwrap(); + writeln!( + config, + "# The keywords COLOR, OPTIONS, and EIGHTBIT (honored by the" + ) + .unwrap(); + writeln!( + config, + "# slackware version of dircolors) are recognized but ignored." + ) + .unwrap(); + writeln!( + config, + "# Global config options can be specified before TERM or COLORTERM entries" + ) + .unwrap(); + writeln!( + config, + "# Below are TERM or COLORTERM entries, which can be glob patterns, which" + ) + .unwrap(); + writeln!( + config, + "# restrict following config to systems with matching environment variables." + ) + .unwrap(); writeln!(config, "COLORTERM ?*").unwrap(); for term in TERMS.iter() { writeln!(config, "TERM {}", term).unwrap(); } // Adding file types and their color codes with header - writeln!(config, "# Below are the color init strings for the basic file types.").unwrap(); - writeln!(config, "# One can use codes for 256 or more colors supported by modern terminals.").unwrap(); - writeln!(config, "# The default color codes use the capabilities of an 8 color terminal").unwrap(); - writeln!(config, "# with some additional attributes as per the following codes:").unwrap(); + writeln!( + config, + "# Below are the color init strings for the basic file types." + ) + .unwrap(); + writeln!( + config, + "# One can use codes for 256 or more colors supported by modern terminals." + ) + .unwrap(); + writeln!( + config, + "# The default color codes use the capabilities of an 8 color terminal" + ) + .unwrap(); + writeln!( + config, + "# with some additional attributes as per the following codes:" + ) + .unwrap(); writeln!(config, "# Attribute codes:").unwrap(); - writeln!(config, "# 00=none 01=bold 04=underscore 05=blink 07=reverse 08=concealed").unwrap(); + writeln!( + config, + "# 00=none 01=bold 04=underscore 05=blink 07=reverse 08=concealed" + ) + .unwrap(); writeln!(config, "# Text color codes:").unwrap(); - writeln!(config, "# 30=black 31=red 32=green 33=yellow 34=blue 35=magenta 36=cyan 37=white").unwrap(); + writeln!( + config, + "# 30=black 31=red 32=green 33=yellow 34=blue 35=magenta 36=cyan 37=white" + ) + .unwrap(); writeln!(config, "# Background color codes:").unwrap(); - writeln!(config, "# 40=black 41=red 42=green 43=yellow 44=blue 45=magenta 46=cyan 47=white").unwrap(); + writeln!( + config, + "# 40=black 41=red 42=green 43=yellow 44=blue 45=magenta 46=cyan 47=white" + ) + .unwrap(); writeln!(config, "#NORMAL 00 # no color code at all").unwrap(); writeln!(config, "#FILE 00 # regular file: use no color at all").unwrap(); @@ -517,14 +552,30 @@ pub fn generate_dircolors_config() -> String { writeln!(config, "{} {}", name, code).unwrap(); } - writeln!(config, "# List any file extensions like '.gz' or '.tar' that you would like ls").unwrap(); - writeln!(config, "# to color below. Put the extension, a space, and the color init string.").unwrap(); + writeln!( + config, + "# List any file extensions like '.gz' or '.tar' that you would like ls" + ) + .unwrap(); + writeln!( + config, + "# to color below. Put the extension, a space, and the color init string." + ) + .unwrap(); for (ext, color) in FILE_COLORS.iter() { writeln!(config, "{} {}", ext, color).unwrap(); } - writeln!(config, "# Subsequent TERM or COLORTERM entries, can be used to add / override").unwrap(); - write!(config, "# config specific to those matching environment variables.").unwrap(); + writeln!( + config, + "# Subsequent TERM or COLORTERM entries, can be used to add / override" + ) + .unwrap(); + write!( + config, + "# config specific to those matching environment variables." + ) + .unwrap(); config } diff --git a/src/uucore/src/lib/features/colors.rs b/src/uucore/src/lib/features/colors.rs index 96ca6d45672..81d117a50b3 100644 --- a/src/uucore/src/lib/features/colors.rs +++ b/src/uucore/src/lib/features/colors.rs @@ -2,7 +2,7 @@ // // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. - +// cSpell:disable use once_cell::sync::Lazy; /* The keywords COLOR, OPTIONS, and EIGHTBIT (honored by the From 4903b91973005f039f468d0f7740daf24b1ce282 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Mon, 6 Nov 2023 09:25:54 +0100 Subject: [PATCH 0437/2851] du: call unused _du_basics() in test_du_basics() --- tests/by-util/test_du.rs | 54 +++++++++++++++++++++++++++++++--------- 1 file changed, 42 insertions(+), 12 deletions(-) diff --git a/tests/by-util/test_du.rs b/tests/by-util/test_du.rs index c07de2851ee..8ed7ce8c0c1 100644 --- a/tests/by-util/test_du.rs +++ b/tests/by-util/test_du.rs @@ -22,24 +22,54 @@ const SUB_LINK: &str = "subdir/links/sublink.txt"; #[test] fn test_du_basics() { - new_ucmd!().succeeds().no_stderr(); + let ts = TestScenario::new(util_name!()); + + let result = ts.ucmd().succeeds(); + + #[cfg(any(target_os = "linux", target_os = "android"))] + { + let result_reference = unwrap_or_return!(expected_result(&ts, &[])); + if result_reference.succeeded() { + assert_eq!(result.stdout_str(), result_reference.stdout_str()); + return; + } + } + _du_basics(result.stdout_str()); } + #[cfg(target_vendor = "apple")] fn _du_basics(s: &str) { - let answer = "32\t./subdir -8\t./subdir/deeper -24\t./subdir/links -40\t. -"; + let answer = concat!( + "4\t./subdir/deeper/deeper_dir\n", + "8\t./subdir/deeper\n", + "12\t./subdir/links\n", + "20\t./subdir\n", + "24\t.\n" + ); + assert_eq!(s, answer); +} + +#[cfg(target_os = "windows")] +fn _du_basics(s: &str) { + let answer = concat!( + "0\t.\\subdir\\deeper\\deeper_dir\n", + "0\t.\\subdir\\deeper\n", + "8\t.\\subdir\\links\n", + "8\t.\\subdir\n", + "8\t.\n" + ); assert_eq!(s, answer); } -#[cfg(not(target_vendor = "apple"))] + +#[cfg(all(not(target_vendor = "apple"), not(target_os = "windows"),))] fn _du_basics(s: &str) { - let answer = "28\t./subdir -8\t./subdir/deeper -16\t./subdir/links -36\t. -"; + let answer = concat!( + "8\t./subdir/deeper/deeper_dir\n", + "16\t./subdir/deeper\n", + "16\t./subdir/links\n", + "36\t./subdir\n", + "44\t.\n" + ); assert_eq!(s, answer); } From 0ea1a7cd88c2a6d02ff4c1e69f5a9fa70c9bc135 Mon Sep 17 00:00:00 2001 From: Nathan Houghton Date: Sat, 21 Oct 2023 20:22:25 -0700 Subject: [PATCH 0438/2851] tests/cp, tests/test: Don't attempt to set sticky file bit on FreeBSD On FreeBSD (and OpenBSD), only the superuser can set the file sticky bit. --- tests/by-util/test_cp.rs | 20 +++++++++++++++----- tests/by-util/test_test.rs | 2 +- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/tests/by-util/test_cp.rs b/tests/by-util/test_cp.rs index 36bca68b0d0..37bec522238 100644 --- a/tests/by-util/test_cp.rs +++ b/tests/by-util/test_cp.rs @@ -13,7 +13,7 @@ use std::os::unix::fs; #[cfg(unix)] use std::os::unix::fs::MetadataExt; -#[cfg(all(unix, not(target_os = "freebsd")))] +#[cfg(unix)] use std::os::unix::fs::PermissionsExt; #[cfg(windows)] use std::os::windows::fs::symlink_file; @@ -2381,13 +2381,18 @@ fn test_copy_symlink_force() { } #[test] -#[cfg(all(unix, not(target_os = "freebsd")))] +#[cfg(unix)] fn test_no_preserve_mode() { use std::os::unix::prelude::MetadataExt; use uucore::mode::get_umask; - const PERMS_ALL: u32 = 0o7777; + const PERMS_ALL: u32 = if cfg!(target_os = "freebsd") { + // Only the superuser can set the sticky bit on a file. + 0o6777 + } else { + 0o7777 + }; let (at, mut ucmd) = at_and_ucmd!(); at.touch("file"); @@ -2407,11 +2412,16 @@ fn test_no_preserve_mode() { } #[test] -#[cfg(all(unix, not(target_os = "freebsd")))] +#[cfg(unix)] fn test_preserve_mode() { use std::os::unix::prelude::MetadataExt; - const PERMS_ALL: u32 = 0o7777; + const PERMS_ALL: u32 = if cfg!(target_os = "freebsd") { + // Only the superuser can set the sticky bit on a file. + 0o6777 + } else { + 0o7777 + }; let (at, mut ucmd) = at_and_ucmd!(); at.touch("file"); diff --git a/tests/by-util/test_test.rs b/tests/by-util/test_test.rs index 922d854c640..b91bc727d40 100644 --- a/tests/by-util/test_test.rs +++ b/tests/by-util/test_test.rs @@ -553,7 +553,7 @@ fn test_nonexistent_file_is_not_symlink() { } #[test] -// FixME: freebsd fails with 'chmod: sticky_file: Inappropriate file type or format' +// Only the superuser is allowed to set the sticky bit on files on FreeBSD. // Windows has no concept of sticky bit #[cfg(not(any(windows, target_os = "freebsd")))] fn test_file_is_sticky() { From 117ab7737ac1aa61ef4759837ee7718ba016f860 Mon Sep 17 00:00:00 2001 From: Arpit Bhadauria Date: Sat, 2 Dec 2023 17:25:57 +0000 Subject: [PATCH 0439/2851] Optimize expr for numerical values --- src/uu/expr/src/expr.rs | 2 +- src/uu/expr/src/syntax_tree.rs | 106 +++++++++++++++++++++------------ 2 files changed, 69 insertions(+), 39 deletions(-) diff --git a/src/uu/expr/src/expr.rs b/src/uu/expr/src/expr.rs index c271f0935fd..91d7a878820 100644 --- a/src/uu/expr/src/expr.rs +++ b/src/uu/expr/src/expr.rs @@ -108,7 +108,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { .map(|v| v.into_iter().map(|s| s.as_ref()).collect::>()) .unwrap_or_default(); - let res = AstNode::parse(&token_strings)?.eval()?; + let res = AstNode::parse(&token_strings)?.eval()?.to_string(); println!("{res}"); if !is_truthy(&res) { return Err(1.into()); diff --git a/src/uu/expr/src/syntax_tree.rs b/src/uu/expr/src/syntax_tree.rs index f81f1da1ec4..705864f3a6c 100644 --- a/src/uu/expr/src/syntax_tree.rs +++ b/src/uu/expr/src/syntax_tree.rs @@ -6,6 +6,7 @@ // spell-checker:ignore (ToDO) ints paren prec multibytes use num_bigint::BigInt; +use num_traits::ToPrimitive; use onig::{Regex, RegexOptions, Syntax}; use crate::{ExprError, ExprResult}; @@ -45,7 +46,7 @@ pub enum StringOp { } impl BinOp { - fn eval(&self, left: &AstNode, right: &AstNode) -> ExprResult { + fn eval(&self, left: &AstNode, right: &AstNode) -> ExprResult { match self { Self::Relation(op) => op.eval(left, right), Self::Numeric(op) => op.eval(left, right), @@ -55,10 +56,10 @@ impl BinOp { } impl RelationOp { - fn eval(&self, a: &AstNode, b: &AstNode) -> ExprResult { + fn eval(&self, a: &AstNode, b: &AstNode) -> ExprResult { let a = a.eval()?; let b = b.eval()?; - let b = if let (Ok(a), Ok(b)) = (a.parse::(), b.parse::()) { + let b = if let (NumOrStr::Num(a), NumOrStr::Num(b)) = (&a, &b) { match self { Self::Lt => a < b, Self::Leq => a <= b, @@ -79,24 +80,22 @@ impl RelationOp { } }; if b { - Ok("1".into()) + Ok(NumOrStr::Num(BigInt::from(1))) } else { - Ok("0".into()) + Ok(NumOrStr::Num(BigInt::from(0))) } } } impl NumericOp { - fn eval(&self, left: &AstNode, right: &AstNode) -> ExprResult { + fn eval(&self, left: &AstNode, right: &AstNode) -> ExprResult { let a: BigInt = left .eval()? - .parse() - .map_err(|_| ExprError::NonIntegerArgument)?; + .to_bigint()?; let b: BigInt = right .eval()? - .parse() - .map_err(|_| ExprError::NonIntegerArgument)?; - Ok(match self { + .to_bigint()?; + Ok(NumOrStr::Num(match self { Self::Add => a + b, Self::Sub => a - b, Self::Mul => a * b, @@ -110,67 +109,66 @@ impl NumericOp { }; a % b } - } - .to_string()) + })) } } impl StringOp { - fn eval(&self, left: &AstNode, right: &AstNode) -> ExprResult { + fn eval(&self, left: &AstNode, right: &AstNode) -> ExprResult { match self { Self::Or => { let left = left.eval()?; - if is_truthy(&left) { + if is_truthy(&left.to_string()) { return Ok(left); } let right = right.eval()?; - if is_truthy(&right) { + if is_truthy(&right.to_string()) { return Ok(right); } - Ok("0".into()) + Ok(NumOrStr::Num(BigInt::from(0))) } Self::And => { let left = left.eval()?; - if !is_truthy(&left) { - return Ok("0".into()); + if !is_truthy(&left.to_string()) { + return Ok(NumOrStr::Num(BigInt::from(0))); } let right = right.eval()?; - if !is_truthy(&right) { - return Ok("0".into()); + if !is_truthy(&right.to_string()) { + return Ok(NumOrStr::Num(BigInt::from(0))); } Ok(left) } Self::Match => { let left = left.eval()?; let right = right.eval()?; - let re_string = format!("^{}", &right); + let re_string = format!("^{}", right.to_string()); let re = Regex::with_options( &re_string, RegexOptions::REGEX_OPTION_NONE, Syntax::grep(), ) .map_err(|_| ExprError::InvalidRegexExpression)?; - Ok(if re.captures_len() > 0 { - re.captures(&left) + Ok(NumOrStr::Str(if re.captures_len() > 0 { + re.captures(&left.to_string()) .map(|captures| captures.at(1).unwrap()) .unwrap_or("") .to_string() } else { - re.find(&left) + re.find(&left.to_string()) .map_or("0".to_string(), |(start, end)| (end - start).to_string()) - }) + })) } Self::Index => { let left = left.eval()?; let right = right.eval()?; - for (current_idx, ch_h) in left.chars().enumerate() { - for ch_n in right.chars() { + for (current_idx, ch_h) in left.to_string().chars().enumerate() { + for ch_n in right.to_string().chars() { if ch_n == ch_h { - return Ok((current_idx + 1).to_string()); + return Ok(NumOrStr::Num(BigInt::from(current_idx + 1))); } } } - Ok("0".to_string()) + Ok(NumOrStr::Num(BigInt::from(0))) } } } @@ -200,6 +198,38 @@ const PRECEDENCE: &[&[(&str, BinOp)]] = &[ &[(":", BinOp::String(StringOp::Match))], ]; +#[derive(Debug, PartialEq, Eq, Ord, PartialOrd)] +pub enum NumOrStr { + Num(BigInt), + Str(String), +} + +impl NumOrStr { + pub fn to_usize(self: NumOrStr) -> Option { + match self.to_bigint() { + Ok(num) => {num.to_usize()} + Err(_) => {None}, + } + } + + pub fn to_string(self: &NumOrStr) -> String { + match self { + NumOrStr::Num(num) => {num.to_string()} + NumOrStr::Str(str) => {str.to_string()}, + } + } + + pub fn to_bigint(self: NumOrStr) -> ExprResult { + match self { + NumOrStr::Num(num) => {Ok(num)} + NumOrStr::Str(str) => { match str.parse::() { + Ok(val) => {Ok(val)}, + Err(_) => {Err(ExprError::NonIntegerArgument)} + }}, + } + } +} + #[derive(Debug, PartialEq, Eq)] pub enum AstNode { Leaf { @@ -225,9 +255,9 @@ impl AstNode { Parser::new(input).parse() } - pub fn eval(&self) -> ExprResult { + pub fn eval(&self) -> ExprResult { match self { - Self::Leaf { value } => Ok(value.into()), + Self::Leaf { value } => Ok(NumOrStr::Str(value.to_string())), Self::BinOp { op_type, left, @@ -238,7 +268,7 @@ impl AstNode { pos, length, } => { - let string = string.eval()?; + let string = string.eval()?.to_string(); // The GNU docs say: // @@ -247,16 +277,16 @@ impl AstNode { // // So we coerce errors into 0 to make that the only case we // have to care about. - let pos: usize = pos.eval()?.parse().unwrap_or(0); - let length: usize = length.eval()?.parse().unwrap_or(0); + let pos: usize = pos.eval()?.to_usize().unwrap_or(0); + let length: usize = length.eval()?.to_usize().unwrap_or(0); let (Some(pos), Some(_)) = (pos.checked_sub(1), length.checked_sub(1)) else { - return Ok(String::new()); + return Ok(NumOrStr::Str(String::new())); }; - Ok(string.chars().skip(pos).take(length).collect()) + Ok(NumOrStr::Str(string.chars().skip(pos).take(length).collect())) } - Self::Length { string } => Ok(string.eval()?.chars().count().to_string()), + Self::Length { string } => Ok(NumOrStr::Num(BigInt::from(string.eval()?.to_string().chars().count()))), } } } From f4141100836ff382b1e217b96c278e1d47e01b74 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 3 Dec 2023 12:49:28 +0000 Subject: [PATCH 0440/2851] chore(deps): update vmactions/freebsd-vm action to v1.0.3 --- .github/workflows/freebsd.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/freebsd.yml b/.github/workflows/freebsd.yml index 02c0137e7b2..5261d103c59 100644 --- a/.github/workflows/freebsd.yml +++ b/.github/workflows/freebsd.yml @@ -39,7 +39,7 @@ jobs: - name: Run sccache-cache uses: mozilla-actions/sccache-action@v0.0.3 - name: Prepare, build and test - uses: vmactions/freebsd-vm@v1.0.2 + uses: vmactions/freebsd-vm@v1.0.3 with: usesh: true sync: rsync @@ -131,7 +131,7 @@ jobs: - name: Run sccache-cache uses: mozilla-actions/sccache-action@v0.0.3 - name: Prepare, build and test - uses: vmactions/freebsd-vm@v1.0.2 + uses: vmactions/freebsd-vm@v1.0.3 with: usesh: true sync: rsync From 49fb72ed21ddcb709426159b1336edded3778164 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Sun, 3 Dec 2023 14:07:03 +0100 Subject: [PATCH 0441/2851] du: disable test on Android --- tests/by-util/test_du.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/by-util/test_du.rs b/tests/by-util/test_du.rs index c07de2851ee..10f32caf52e 100644 --- a/tests/by-util/test_du.rs +++ b/tests/by-util/test_du.rs @@ -441,6 +441,7 @@ fn test_du_inodes() { } } +#[cfg(not(target_os = "android"))] #[test] fn test_du_inodes_with_count_links() { let ts = TestScenario::new(util_name!()); From a6d15d67caf13c86aa2973ea45a7ed7758c8f50b Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Fri, 1 Dec 2023 15:15:44 +0100 Subject: [PATCH 0442/2851] ls: cleanup "spell-checker:ignore" entries --- src/uu/ls/src/ls.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index cba9cdf5375..6e7fe405b16 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -3,7 +3,7 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// spell-checker:ignore (ToDO) cpio svgz webm somegroup nlink rmvb xspf tabsize dired subdired dtype +// spell-checker:ignore (ToDO) somegroup nlink tabsize dired subdired dtype use clap::{ builder::{NonEmptyStringValueParser, ValueParser}, From c77d389f5b93399bdeb6007f24d745c94e64f454 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Fri, 1 Dec 2023 16:19:13 +0100 Subject: [PATCH 0443/2851] ls: improve some var names related to block sizes --- src/uu/ls/src/ls.rs | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 6e7fe405b16..3745cfc9df3 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -741,14 +741,14 @@ impl Config { let mut needs_color = extract_color(options); - let cmd_line_bs = options.get_one::(options::size::BLOCK_SIZE); - let opt_si = cmd_line_bs.is_some() + let opt_block_size = options.get_one::(options::size::BLOCK_SIZE); + let opt_si = opt_block_size.is_some() && options .get_one::(options::size::BLOCK_SIZE) .unwrap() .eq("si") || options.get_flag(options::size::SI); - let opt_hr = (cmd_line_bs.is_some() + let opt_hr = (opt_block_size.is_some() && options .get_one::(options::size::BLOCK_SIZE) .unwrap() @@ -756,9 +756,9 @@ impl Config { || options.get_flag(options::size::HUMAN_READABLE); let opt_kb = options.get_flag(options::size::KIBIBYTES); - let bs_env_var = std::env::var_os("BLOCK_SIZE"); - let ls_bs_env_var = std::env::var_os("LS_BLOCK_SIZE"); - let pc_env_var = std::env::var_os("POSIXLY_CORRECT"); + let env_var_block_size = std::env::var_os("BLOCK_SIZE"); + let env_var_ls_block_size = std::env::var_os("LS_BLOCK_SIZE"); + let env_var_posixly_correct = std::env::var_os("POSIXLY_CORRECT"); let size_format = if opt_si { SizeFormat::Decimal @@ -768,13 +768,13 @@ impl Config { SizeFormat::Bytes }; - let raw_bs = if let Some(cmd_line_bs) = cmd_line_bs { - OsString::from(cmd_line_bs) + let raw_block_size = if let Some(opt_block_size) = opt_block_size { + OsString::from(opt_block_size) } else if !opt_kb { - if let Some(ls_bs_env_var) = ls_bs_env_var { - ls_bs_env_var - } else if let Some(bs_env_var) = bs_env_var { - bs_env_var + if let Some(env_var_ls_block_size) = env_var_ls_block_size { + env_var_ls_block_size + } else if let Some(env_var_block_size) = env_var_block_size { + env_var_block_size } else { OsString::from("") } @@ -782,15 +782,17 @@ impl Config { OsString::from("") }; - let block_size: Option = if !opt_si && !opt_hr && !raw_bs.is_empty() { - match parse_size_u64(&raw_bs.to_string_lossy()) { + let block_size: Option = if !opt_si && !opt_hr && !raw_block_size.is_empty() { + match parse_size_u64(&raw_block_size.to_string_lossy()) { Ok(size) => Some(size), Err(_) => { - show!(LsError::BlockSizeParseError(cmd_line_bs.unwrap().clone())); + show!(LsError::BlockSizeParseError( + opt_block_size.unwrap().clone() + )); None } } - } else if let Some(pc) = pc_env_var { + } else if let Some(pc) = env_var_posixly_correct { if pc.as_os_str() == OsStr::new("true") || pc == OsStr::new("1") { Some(POSIXLY_CORRECT_BLOCK_SIZE) } else { From 51fc2d7564b29de22936e8ca4a73883aeca77662 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Fri, 1 Dec 2023 16:27:05 +0100 Subject: [PATCH 0444/2851] ls: ignore value of POSIXLY_CORRECT --- src/uu/ls/src/ls.rs | 10 +++------- tests/by-util/test_ls.rs | 27 +++++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 3745cfc9df3..f645e31d3c2 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -20,7 +20,7 @@ use std::os::windows::fs::MetadataExt; use std::{ cmp::Reverse, error::Error, - ffi::{OsStr, OsString}, + ffi::OsString, fmt::{Display, Write as FmtWrite}, fs::{self, DirEntry, FileType, Metadata, ReadDir}, io::{stdout, BufWriter, ErrorKind, Stdout, Write}, @@ -792,12 +792,8 @@ impl Config { None } } - } else if let Some(pc) = env_var_posixly_correct { - if pc.as_os_str() == OsStr::new("true") || pc == OsStr::new("1") { - Some(POSIXLY_CORRECT_BLOCK_SIZE) - } else { - None - } + } else if env_var_posixly_correct.is_some() { + Some(POSIXLY_CORRECT_BLOCK_SIZE) } else { None }; diff --git a/tests/by-util/test_ls.rs b/tests/by-util/test_ls.rs index 19a3f5578fa..fcd57170d48 100644 --- a/tests/by-util/test_ls.rs +++ b/tests/by-util/test_ls.rs @@ -3828,3 +3828,30 @@ fn test_ls_cf_output_should_be_delimited_by_tab() { .succeeds() .stdout_is("a2345/\tb/\n"); } + +#[cfg(all(unix, feature = "dd"))] +#[test] +fn test_posixly_correct() { + let scene = TestScenario::new(util_name!()); + + scene + .ccmd("dd") + .arg("if=/dev/zero") + .arg("of=file") + .arg("bs=1024") + .arg("count=1") + .succeeds(); + + scene + .ucmd() + .arg("-s") + .succeeds() + .stdout_contains_line("total 4"); + + scene + .ucmd() + .arg("-s") + .env("POSIXLY_CORRECT", "some_value") + .succeeds() + .stdout_contains_line("total 8"); +} From d8a64a90ece80fc029860f66d7bf8858c79f9e91 Mon Sep 17 00:00:00 2001 From: Arpit Bhadauria Date: Sun, 3 Dec 2023 15:09:12 +0000 Subject: [PATCH 0445/2851] Formatting fixes in expr --- src/uu/expr/src/syntax_tree.rs | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/uu/expr/src/syntax_tree.rs b/src/uu/expr/src/syntax_tree.rs index 705864f3a6c..1c74b97103b 100644 --- a/src/uu/expr/src/syntax_tree.rs +++ b/src/uu/expr/src/syntax_tree.rs @@ -89,12 +89,8 @@ impl RelationOp { impl NumericOp { fn eval(&self, left: &AstNode, right: &AstNode) -> ExprResult { - let a: BigInt = left - .eval()? - .to_bigint()?; - let b: BigInt = right - .eval()? - .to_bigint()?; + let a: BigInt = left.eval()?.to_bigint()?; + let b: BigInt = right.eval()?.to_bigint()?; Ok(NumOrStr::Num(match self { Self::Add => a + b, Self::Sub => a - b, @@ -207,25 +203,25 @@ pub enum NumOrStr { impl NumOrStr { pub fn to_usize(self: NumOrStr) -> Option { match self.to_bigint() { - Ok(num) => {num.to_usize()} - Err(_) => {None}, + Ok(num) => num.to_usize(), + Err(_) => None, } } pub fn to_string(self: &NumOrStr) -> String { match self { - NumOrStr::Num(num) => {num.to_string()} - NumOrStr::Str(str) => {str.to_string()}, + NumOrStr::Num(num) => num.to_string(), + NumOrStr::Str(str) => str.to_string(), } } pub fn to_bigint(self: NumOrStr) -> ExprResult { match self { - NumOrStr::Num(num) => {Ok(num)} - NumOrStr::Str(str) => { match str.parse::() { - Ok(val) => {Ok(val)}, - Err(_) => {Err(ExprError::NonIntegerArgument)} - }}, + NumOrStr::Num(num) => Ok(num), + NumOrStr::Str(str) => match str.parse::() { + Ok(val) => Ok(val), + Err(_) => Err(ExprError::NonIntegerArgument), + }, } } } @@ -284,9 +280,13 @@ impl AstNode { return Ok(NumOrStr::Str(String::new())); }; - Ok(NumOrStr::Str(string.chars().skip(pos).take(length).collect())) + Ok(NumOrStr::Str( + string.chars().skip(pos).take(length).collect(), + )) } - Self::Length { string } => Ok(NumOrStr::Num(BigInt::from(string.eval()?.to_string().chars().count()))), + Self::Length { string } => Ok(NumOrStr::Num(BigInt::from( + string.eval()?.to_string().chars().count(), + ))), } } } From f8573d555133f22cfdfbfa198a265138277d3f36 Mon Sep 17 00:00:00 2001 From: Arpit Bhadauria Date: Sun, 3 Dec 2023 20:03:50 +0000 Subject: [PATCH 0446/2851] code and styling fixes in expr --- src/uu/expr/src/expr.rs | 4 +- src/uu/expr/src/syntax_tree.rs | 126 +++++++++++++++++++++------------ 2 files changed, 81 insertions(+), 49 deletions(-) diff --git a/src/uu/expr/src/expr.rs b/src/uu/expr/src/expr.rs index 91d7a878820..b46034f845d 100644 --- a/src/uu/expr/src/expr.rs +++ b/src/uu/expr/src/expr.rs @@ -13,7 +13,7 @@ use uucore::{ format_usage, help_about, help_section, help_usage, }; -use crate::syntax_tree::is_truthy; +use crate::syntax_tree::{is_truthy, NumOrStr}; mod syntax_tree; @@ -110,7 +110,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let res = AstNode::parse(&token_strings)?.eval()?.to_string(); println!("{res}"); - if !is_truthy(&res) { + if !is_truthy(&NumOrStr::from(res)) { return Err(1.into()); } Ok(()) diff --git a/src/uu/expr/src/syntax_tree.rs b/src/uu/expr/src/syntax_tree.rs index 1c74b97103b..79ba8d9ae10 100644 --- a/src/uu/expr/src/syntax_tree.rs +++ b/src/uu/expr/src/syntax_tree.rs @@ -57,8 +57,8 @@ impl BinOp { impl RelationOp { fn eval(&self, a: &AstNode, b: &AstNode) -> ExprResult { - let a = a.eval()?; - let b = b.eval()?; + let a = a.eval()?.coerce_num(); + let b = b.eval()?.coerce_num(); let b = if let (NumOrStr::Num(a), NumOrStr::Num(b)) = (&a, &b) { match self { Self::Lt => a < b, @@ -80,17 +80,17 @@ impl RelationOp { } }; if b { - Ok(NumOrStr::Num(BigInt::from(1))) + Ok(NumOrStr::from(1)) } else { - Ok(NumOrStr::Num(BigInt::from(0))) + Ok(NumOrStr::from(0)) } } } impl NumericOp { fn eval(&self, left: &AstNode, right: &AstNode) -> ExprResult { - let a: BigInt = left.eval()?.to_bigint()?; - let b: BigInt = right.eval()?.to_bigint()?; + let a = left.eval()?.to_bigint()?; + let b = right.eval()?.to_bigint()?; Ok(NumOrStr::Num(match self { Self::Add => a + b, Self::Sub => a - b, @@ -114,23 +114,23 @@ impl StringOp { match self { Self::Or => { let left = left.eval()?; - if is_truthy(&left.to_string()) { + if is_truthy(&left) { return Ok(left); } let right = right.eval()?; - if is_truthy(&right.to_string()) { + if is_truthy(&right) { return Ok(right); } - Ok(NumOrStr::Num(BigInt::from(0))) + Ok(NumOrStr::from(0)) } Self::And => { let left = left.eval()?; - if !is_truthy(&left.to_string()) { - return Ok(NumOrStr::Num(BigInt::from(0))); + if !is_truthy(&left) { + return Ok(NumOrStr::from(0)); } let right = right.eval()?; - if !is_truthy(&right.to_string()) { - return Ok(NumOrStr::Num(BigInt::from(0))); + if !is_truthy(&right) { + return Ok(NumOrStr::from(0)); } Ok(left) } @@ -144,7 +144,7 @@ impl StringOp { Syntax::grep(), ) .map_err(|_| ExprError::InvalidRegexExpression)?; - Ok(NumOrStr::Str(if re.captures_len() > 0 { + Ok(NumOrStr::from(if re.captures_len() > 0 { re.captures(&left.to_string()) .map(|captures| captures.at(1).unwrap()) .unwrap_or("") @@ -155,16 +155,16 @@ impl StringOp { })) } Self::Index => { - let left = left.eval()?; - let right = right.eval()?; - for (current_idx, ch_h) in left.to_string().chars().enumerate() { + let left = left.eval()?.to_string(); + let right = right.eval()?.to_string(); + for (current_idx, ch_h) in left.chars().enumerate() { for ch_n in right.to_string().chars() { if ch_n == ch_h { - return Ok(NumOrStr::Num(BigInt::from(current_idx + 1))); + return Ok(NumOrStr::from(current_idx + 1)); } } } - Ok(NumOrStr::Num(BigInt::from(0))) + Ok(NumOrStr::from(0)) } } } @@ -200,27 +200,54 @@ pub enum NumOrStr { Str(String), } +impl From for NumOrStr { + fn from(num: usize) -> NumOrStr { + NumOrStr::Num(BigInt::from(num)) + } +} + +impl From for NumOrStr { + fn from(num: BigInt) -> NumOrStr { + NumOrStr::Num(num) + } +} + +impl From for NumOrStr { + fn from(str: String) -> NumOrStr { + NumOrStr::Str(str) + } +} + impl NumOrStr { - pub fn to_usize(self: NumOrStr) -> Option { + pub fn to_usize(self: Self) -> Option { match self.to_bigint() { Ok(num) => num.to_usize(), Err(_) => None, } } - pub fn to_string(self: &NumOrStr) -> String { + pub fn to_string(self: Self) -> String { match self { - NumOrStr::Num(num) => num.to_string(), - NumOrStr::Str(str) => str.to_string(), + Self::Num(num) => num.to_string(), + Self::Str(str) => str.to_string(), } } - pub fn to_bigint(self: NumOrStr) -> ExprResult { + pub fn to_bigint(self: Self) -> ExprResult { match self { - NumOrStr::Num(num) => Ok(num), - NumOrStr::Str(str) => match str.parse::() { - Ok(val) => Ok(val), - Err(_) => Err(ExprError::NonIntegerArgument), + Self::Num(num) => Ok(num), + Self::Str(str) => str + .parse::() + .map_err(|_| ExprError::NonIntegerArgument), + } + } + + pub fn coerce_num(self: Self) -> NumOrStr { + match self { + Self::Num(num) => Self::from(num), + Self::Str(str) => match str.parse::() { + Ok(num) => Self::from(num), + Err(_) => Self::from(str), }, } } @@ -253,7 +280,7 @@ impl AstNode { pub fn eval(&self) -> ExprResult { match self { - Self::Leaf { value } => Ok(NumOrStr::Str(value.to_string())), + Self::Leaf { value } => Ok(NumOrStr::from(value.to_string())), Self::BinOp { op_type, left, @@ -277,16 +304,16 @@ impl AstNode { let length: usize = length.eval()?.to_usize().unwrap_or(0); let (Some(pos), Some(_)) = (pos.checked_sub(1), length.checked_sub(1)) else { - return Ok(NumOrStr::Str(String::new())); + return Ok(NumOrStr::from(String::new())); }; - Ok(NumOrStr::Str( - string.chars().skip(pos).take(length).collect(), + Ok(NumOrStr::from( + string.chars().skip(pos).take(length).collect::(), )) } - Self::Length { string } => Ok(NumOrStr::Num(BigInt::from( - string.eval()?.to_string().chars().count(), - ))), + Self::Length { string } => { + Ok(NumOrStr::from(string.eval()?.to_string().chars().count())) + } } } } @@ -429,21 +456,26 @@ impl<'a> Parser<'a> { /// Determine whether `expr` should evaluate the string as "truthy" /// /// Truthy strings are either empty or match the regex "-?0+". -pub fn is_truthy(s: &str) -> bool { - // Edge case: `-` followed by nothing is truthy - if s == "-" { - return true; - } +pub fn is_truthy(s: &NumOrStr) -> bool { + match s { + NumOrStr::Num(num) => num == &BigInt::from(0), + NumOrStr::Str(str) => { + // Edge case: `-` followed by nothing is truthy + if str == "-" { + return true; + } - let mut bytes = s.bytes(); + let mut bytes = str.bytes(); - // Empty string is falsy - let Some(first) = bytes.next() else { - return false; - }; + // Empty string is falsy + let Some(first) = bytes.next() else { + return false; + }; - let is_zero = (first == b'-' || first == b'0') && bytes.all(|b| b == b'0'); - !is_zero + let is_zero = (first == b'-' || first == b'0') && bytes.all(|b| b == b'0'); + !is_zero + } + } } #[cfg(test)] From 5672e3d9bdec3acc1d1ab22b0217b5fac17ab10b Mon Sep 17 00:00:00 2001 From: Arpit Bhadauria Date: Sun, 3 Dec 2023 22:07:56 +0000 Subject: [PATCH 0447/2851] Fix errors --- src/uu/expr/src/syntax_tree.rs | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/src/uu/expr/src/syntax_tree.rs b/src/uu/expr/src/syntax_tree.rs index 79ba8d9ae10..a4cb99a8365 100644 --- a/src/uu/expr/src/syntax_tree.rs +++ b/src/uu/expr/src/syntax_tree.rs @@ -5,7 +5,7 @@ // spell-checker:ignore (ToDO) ints paren prec multibytes -use num_bigint::BigInt; +use num_bigint::{BigInt, ParseBigIntError}; use num_traits::ToPrimitive; use onig::{Regex, RegexOptions, Syntax}; @@ -57,9 +57,9 @@ impl BinOp { impl RelationOp { fn eval(&self, a: &AstNode, b: &AstNode) -> ExprResult { - let a = a.eval()?.coerce_num(); - let b = b.eval()?.coerce_num(); - let b = if let (NumOrStr::Num(a), NumOrStr::Num(b)) = (&a, &b) { + let a = a.eval()?; + let b = b.eval()?; + let b = if let (Ok(a), Ok(b)) = (&a.coerce_bigint(), &b.coerce_bigint()) { match self { Self::Lt => a < b, Self::Leq => a <= b, @@ -242,13 +242,10 @@ impl NumOrStr { } } - pub fn coerce_num(self: Self) -> NumOrStr { + pub fn coerce_bigint(self: &Self) -> Result { match self { - Self::Num(num) => Self::from(num), - Self::Str(str) => match str.parse::() { - Ok(num) => Self::from(num), - Err(_) => Self::from(str), - }, + Self::Num(num) => Ok(num.clone()), + Self::Str(str) => str.parse::(), } } } @@ -458,7 +455,7 @@ impl<'a> Parser<'a> { /// Truthy strings are either empty or match the regex "-?0+". pub fn is_truthy(s: &NumOrStr) -> bool { match s { - NumOrStr::Num(num) => num == &BigInt::from(0), + NumOrStr::Num(num) => num != &BigInt::from(0), NumOrStr::Str(str) => { // Edge case: `-` followed by nothing is truthy if str == "-" { From 21c041fa79b64d9f55b8672c4a74e22dd941fd96 Mon Sep 17 00:00:00 2001 From: Arpit Bhadauria Date: Sun, 3 Dec 2023 22:27:13 +0000 Subject: [PATCH 0448/2851] Fix lint issues in expr --- src/uu/expr/src/syntax_tree.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/uu/expr/src/syntax_tree.rs b/src/uu/expr/src/syntax_tree.rs index a4cb99a8365..4f447e60f54 100644 --- a/src/uu/expr/src/syntax_tree.rs +++ b/src/uu/expr/src/syntax_tree.rs @@ -201,39 +201,39 @@ pub enum NumOrStr { } impl From for NumOrStr { - fn from(num: usize) -> NumOrStr { - NumOrStr::Num(BigInt::from(num)) + fn from(num: usize) -> Self { + Self::Num(BigInt::from(num)) } } impl From for NumOrStr { - fn from(num: BigInt) -> NumOrStr { - NumOrStr::Num(num) + fn from(num: BigInt) -> Self { + Self::Num(num) } } impl From for NumOrStr { - fn from(str: String) -> NumOrStr { - NumOrStr::Str(str) + fn from(str: String) -> Self { + Self::Str(str) } } impl NumOrStr { - pub fn to_usize(self: Self) -> Option { + pub fn to_usize(self) -> Option { match self.to_bigint() { Ok(num) => num.to_usize(), Err(_) => None, } } - pub fn to_string(self: Self) -> String { + pub fn to_string(self) -> String { match self { Self::Num(num) => num.to_string(), Self::Str(str) => str.to_string(), } } - pub fn to_bigint(self: Self) -> ExprResult { + pub fn to_bigint(self) -> ExprResult { match self { Self::Num(num) => Ok(num), Self::Str(str) => str @@ -242,7 +242,7 @@ impl NumOrStr { } } - pub fn coerce_bigint(self: &Self) -> Result { + pub fn coerce_bigint(&self) -> Result { match self { Self::Num(num) => Ok(num.clone()), Self::Str(str) => str.parse::(), From 9ecd6a296e06b6f20a5bc29f876a546cdafd020d Mon Sep 17 00:00:00 2001 From: Arpit Bhadauria Date: Sun, 3 Dec 2023 23:32:51 +0000 Subject: [PATCH 0449/2851] Refactoring for lint issues --- src/uu/expr/src/expr.rs | 2 +- src/uu/expr/src/syntax_tree.rs | 53 ++++++++++++++++++++-------------- 2 files changed, 32 insertions(+), 23 deletions(-) diff --git a/src/uu/expr/src/expr.rs b/src/uu/expr/src/expr.rs index b46034f845d..1a9bb07de4a 100644 --- a/src/uu/expr/src/expr.rs +++ b/src/uu/expr/src/expr.rs @@ -108,7 +108,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { .map(|v| v.into_iter().map(|s| s.as_ref()).collect::>()) .unwrap_or_default(); - let res = AstNode::parse(&token_strings)?.eval()?.to_string(); + let res: String = AstNode::parse(&token_strings)?.eval()?.into(); println!("{res}"); if !is_truthy(&NumOrStr::from(res)) { return Err(1.into()); diff --git a/src/uu/expr/src/syntax_tree.rs b/src/uu/expr/src/syntax_tree.rs index 4f447e60f54..7677b5e7e54 100644 --- a/src/uu/expr/src/syntax_tree.rs +++ b/src/uu/expr/src/syntax_tree.rs @@ -59,7 +59,7 @@ impl RelationOp { fn eval(&self, a: &AstNode, b: &AstNode) -> ExprResult { let a = a.eval()?; let b = b.eval()?; - let b = if let (Ok(a), Ok(b)) = (&a.coerce_bigint(), &b.coerce_bigint()) { + let b = if let (Ok(a), Ok(b)) = (&a.to_bigint(), &b.to_bigint()) { match self { Self::Lt => a < b, Self::Leq => a <= b, @@ -89,8 +89,8 @@ impl RelationOp { impl NumericOp { fn eval(&self, left: &AstNode, right: &AstNode) -> ExprResult { - let a = left.eval()?.to_bigint()?; - let b = right.eval()?.to_bigint()?; + let a = >>::into(left.eval()?)?; + let b = >>::into(right.eval()?)?; Ok(NumOrStr::Num(match self { Self::Add => a + b, Self::Sub => a - b, @@ -135,9 +135,9 @@ impl StringOp { Ok(left) } Self::Match => { - let left = left.eval()?; - let right = right.eval()?; - let re_string = format!("^{}", right.to_string()); + let left: String = left.eval()?.into(); + let right: String = right.eval()?.into(); + let re_string = format!("^{}", right); let re = Regex::with_options( &re_string, RegexOptions::REGEX_OPTION_NONE, @@ -145,18 +145,18 @@ impl StringOp { ) .map_err(|_| ExprError::InvalidRegexExpression)?; Ok(NumOrStr::from(if re.captures_len() > 0 { - re.captures(&left.to_string()) + re.captures(&left) .map(|captures| captures.at(1).unwrap()) .unwrap_or("") .to_string() } else { - re.find(&left.to_string()) + re.find(&left) .map_or("0".to_string(), |(start, end)| (end - start).to_string()) })) } Self::Index => { - let left = left.eval()?.to_string(); - let right = right.eval()?.to_string(); + let left: String = left.eval()?.into(); + let right: String = right.eval()?.into(); for (current_idx, ch_h) in left.chars().enumerate() { for ch_n in right.to_string().chars() { if ch_n == ch_h { @@ -218,22 +218,26 @@ impl From for NumOrStr { } } -impl NumOrStr { - pub fn to_usize(self) -> Option { - match self.to_bigint() { +impl Into> for NumOrStr { + fn into(self) -> Option { + match self.into() { Ok(num) => num.to_usize(), Err(_) => None, } } +} - pub fn to_string(self) -> String { +impl Into for NumOrStr { + fn into(self) -> String { match self { Self::Num(num) => num.to_string(), Self::Str(str) => str.to_string(), } } +} - pub fn to_bigint(self) -> ExprResult { +impl Into> for NumOrStr { + fn into(self) -> ExprResult { match self { Self::Num(num) => Ok(num), Self::Str(str) => str @@ -241,8 +245,10 @@ impl NumOrStr { .map_err(|_| ExprError::NonIntegerArgument), } } +} - pub fn coerce_bigint(&self) -> Result { +impl NumOrStr { + pub fn to_bigint(&self) -> Result { match self { Self::Num(num) => Ok(num.clone()), Self::Str(str) => str.parse::(), @@ -288,7 +294,7 @@ impl AstNode { pos, length, } => { - let string = string.eval()?.to_string(); + let string: String = string.eval()?.into(); // The GNU docs say: // @@ -297,8 +303,9 @@ impl AstNode { // // So we coerce errors into 0 to make that the only case we // have to care about. - let pos: usize = pos.eval()?.to_usize().unwrap_or(0); - let length: usize = length.eval()?.to_usize().unwrap_or(0); + let pos: usize = >>::into(pos.eval()?).unwrap_or(0); + let length: usize = + >>::into(length.eval()?).unwrap_or(0); let (Some(pos), Some(_)) = (pos.checked_sub(1), length.checked_sub(1)) else { return Ok(NumOrStr::from(String::new())); @@ -308,9 +315,11 @@ impl AstNode { string.chars().skip(pos).take(length).collect::(), )) } - Self::Length { string } => { - Ok(NumOrStr::from(string.eval()?.to_string().chars().count())) - } + Self::Length { string } => Ok(NumOrStr::from( + >::into(string.eval()?) + .chars() + .count(), + )), } } } From cf853df2dcf4309049ad3a0ff3ab531aabcb86ec Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Mon, 4 Dec 2023 14:48:25 +0100 Subject: [PATCH 0450/2851] ls: remove "#[allow(unused_variables)]" --- src/uu/ls/src/ls.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index cba9cdf5375..626e17ca14d 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -2946,7 +2946,6 @@ fn classify_file(path: &PathData, out: &mut BufWriter) -> Option { /// /// Note that non-unicode sequences in symlink targets are dealt with using /// [`std::path::Path::to_string_lossy`]. -#[allow(unused_variables)] #[allow(clippy::cognitive_complexity)] fn display_file_name( path: &PathData, From d4b22a192d9c4af1ed2b92c0b0197a790536e981 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 4 Dec 2023 19:09:30 +0000 Subject: [PATCH 0451/2851] chore(deps): update vmactions/freebsd-vm action to v1.0.4 --- .github/workflows/freebsd.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/freebsd.yml b/.github/workflows/freebsd.yml index 5261d103c59..27537cf6dab 100644 --- a/.github/workflows/freebsd.yml +++ b/.github/workflows/freebsd.yml @@ -39,7 +39,7 @@ jobs: - name: Run sccache-cache uses: mozilla-actions/sccache-action@v0.0.3 - name: Prepare, build and test - uses: vmactions/freebsd-vm@v1.0.3 + uses: vmactions/freebsd-vm@v1.0.4 with: usesh: true sync: rsync @@ -131,7 +131,7 @@ jobs: - name: Run sccache-cache uses: mozilla-actions/sccache-action@v0.0.3 - name: Prepare, build and test - uses: vmactions/freebsd-vm@v1.0.3 + uses: vmactions/freebsd-vm@v1.0.4 with: usesh: true sync: rsync From 9ef43191ff28af359989848dc65548aa253e4337 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Mon, 4 Dec 2023 21:20:47 +0100 Subject: [PATCH 0452/2851] Document that \0NNN and \xHH need more work --- fuzz/fuzz_targets/fuzz_echo.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/fuzz/fuzz_targets/fuzz_echo.rs b/fuzz/fuzz_targets/fuzz_echo.rs index 826fd6da3eb..fda7fd7276f 100644 --- a/fuzz/fuzz_targets/fuzz_echo.rs +++ b/fuzz/fuzz_targets/fuzz_echo.rs @@ -51,6 +51,7 @@ fn generate_escape_sequence(rng: &mut impl Rng) -> String { let escape_sequences = [ "\\\\", "\\a", "\\b", "\\c", "\\e", "\\f", "\\n", "\\r", "\\t", "\\v", "\\0NNN", "\\xHH", ]; + // \0NNN and \xHH need more work escape_sequences.choose(rng).unwrap().to_string() } From f5776bc511ec0fbe5dac93af190ef8a3c8d610be Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Mon, 4 Dec 2023 22:40:18 +0100 Subject: [PATCH 0453/2851] fix comment Co-authored-by: Terts Diepraam --- src/uucore/src/lib/features/colors.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/uucore/src/lib/features/colors.rs b/src/uucore/src/lib/features/colors.rs index 81d117a50b3..1f55db5a7d2 100644 --- a/src/uucore/src/lib/features/colors.rs +++ b/src/uucore/src/lib/features/colors.rs @@ -78,11 +78,11 @@ pub static FILE_TYPES: Lazy> = L ] }); -/* -# List any file extensions like '.gz' or '.tar' that you would like ls -# to color below. Put the extension, a space, and the color init string. -# (and any comments you want to add after a '#') -*/ +/// Colors for file types +/// +/// List any file extensions like '.gz' or '.tar' that you would like ls +/// to color below. Put the extension, a space, and the color init string. +/// (and any comments you want to add after a '#') pub static FILE_COLORS: Lazy> = Lazy::new(|| { vec![ /* From dabbcff9fbbbce7c0ec692431f73fb17657c8199 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 3 Dec 2023 10:34:33 +0100 Subject: [PATCH 0454/2851] dircolors: manage the --print-ls-colors pipe option --- src/uu/dircolors/src/dircolors.rs | 17 +++++++++++++---- tests/by-util/test_dircolors.rs | 10 ++++++++++ 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/src/uu/dircolors/src/dircolors.rs b/src/uu/dircolors/src/dircolors.rs index 28d74775db8..cf8ed62922c 100644 --- a/src/uu/dircolors/src/dircolors.rs +++ b/src/uu/dircolors/src/dircolors.rs @@ -9,6 +9,7 @@ use std::borrow::Borrow; use std::env; use std::fmt::Write; use std::fs::File; +use std::io::IsTerminal; use std::io::{BufRead, BufReader}; use std::path::Path; @@ -192,9 +193,16 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let result; if files.is_empty() { - println!("{}", generate_ls_colors(&out_format, ":")); - - return Ok(()); + // Check if data is being piped into the program + if std::io::stdin().is_terminal() { + // No data piped, use default behavior + println!("{}", generate_ls_colors(&out_format, ":")); + return Ok(()); + } else { + // Data is piped, process the input from stdin + let fin = BufReader::new(std::io::stdin()); + result = parse(fin.lines().map_while(Result::ok), &out_format, "-"); + } } else if files.len() > 1 { return Err(UUsageError::new( 1, @@ -376,7 +384,8 @@ where let (key, val) = line.split_two(); if val.is_empty() { return Err(format!( - "{}:{}: invalid line; missing second token", + // The double space is what GNU is doing + "{}:{}: invalid line; missing second token", fp.maybe_quote(), num )); diff --git a/tests/by-util/test_dircolors.rs b/tests/by-util/test_dircolors.rs index d4fa0a3b0a3..e3752fcde98 100644 --- a/tests/by-util/test_dircolors.rs +++ b/tests/by-util/test_dircolors.rs @@ -159,6 +159,16 @@ fn test_quoting() { .no_stderr(); } +#[test] +fn test_print_ls_colors() { + new_ucmd!() + .pipe_in("OWT 40;33\n") + .args(&["--print-ls-colors"]) + .succeeds() + .stdout_is("\x1B[40;33mtw\t40;33\x1B[0m\n") + .no_stderr(); +} + #[test] fn test_extra_operand() { new_ucmd!() From 3e354109076eb1f806715e0d5c89006fd2a1a12a Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Mon, 4 Dec 2023 22:47:42 +0100 Subject: [PATCH 0455/2851] dircolors: fix comments --- src/uucore/src/lib/features/colors.rs | 512 +++++++++++++------------- 1 file changed, 250 insertions(+), 262 deletions(-) diff --git a/src/uucore/src/lib/features/colors.rs b/src/uucore/src/lib/features/colors.rs index 1f55db5a7d2..81d2fd3f336 100644 --- a/src/uucore/src/lib/features/colors.rs +++ b/src/uucore/src/lib/features/colors.rs @@ -3,274 +3,262 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. // cSpell:disable -use once_cell::sync::Lazy; -/* The keywords COLOR, OPTIONS, and EIGHTBIT (honored by the - * slackware version of dircolors) are recognized but ignored. - * Global config options can be specified before TERM or COLORTERM entries - * below are TERM or COLORTERM entries, which can be glob patterns, which - * restrict following config to systems with matching environment variables. -*/ -pub static TERMS: Lazy> = Lazy::new(|| { - vec![ - "Eterm", - "ansi", - "*color*", - "con[0-9]*x[0-9]*", - "cons25", - "console", - "cygwin", - "*direct*", - "dtterm", - "gnome", - "hurd", - "jfbterm", - "konsole", - "kterm", - "linux", - "linux-c", - "mlterm", - "putty", - "rxvt*", - "screen*", - "st", - "terminator", - "tmux*", - "vt100", - "xterm*", - ] -}); +/// The keywords COLOR, OPTIONS, and EIGHTBIT (honored by the +/// slackware version of dircolors) are recognized but ignored. +/// Global config options can be specified before TERM or COLORTERM entries +/// below are TERM or COLORTERM entries, which can be glob patterns, which +/// restrict following config to systems with matching environment variables. +pub static TERMS: &[&str] = &[ + "Eterm", + "ansi", + "*color*", + "con[0-9]*x[0-9]*", + "cons25", + "console", + "cygwin", + "*direct*", + "dtterm", + "gnome", + "hurd", + "jfbterm", + "konsole", + "kterm", + "linux", + "linux-c", + "mlterm", + "putty", + "rxvt*", + "screen*", + "st", + "terminator", + "tmux*", + "vt100", + "xterm*", +]; -/* -# Below are the color init strings for the basic file types. -# One can use codes for 256 or more colors supported by modern terminals. -# The default color codes use the capabilities of an 8 color terminal -# with some additional attributes as per the following codes: -# Attribute codes: -# 00=none 01=bold 04=underscore 05=blink 07=reverse 08=concealed -# Text color codes: -# 30=black 31=red 32=green 33=yellow 34=blue 35=magenta 36=cyan 37=white -# Background color codes: -# 40=black 41=red 42=green 43=yellow 44=blue 45=magenta 46=cyan 47=white -#NORMAL 00 # no color code at all -#FILE 00 # regular file: use no color at all -*/ -pub static FILE_TYPES: Lazy> = Lazy::new(|| { - vec![ - ("RESET", "rs", "0"), // reset to "normal" color - ("DIR", "di", "01;34"), // directory - ("LINK", "ln", "01;36"), // symbolic link - ("MULTIHARDLINK", "mh", "00"), // regular file with more than one link - ("FIFO", "pi", "40;33"), // pipe - ("SOCK", "so", "01;35"), // socket - ("DOOR", "do", "01;35"), // door - ("BLK", "bd", "40;33;01"), // block device driver - ("CHR", "cd", "40;33;01"), // character device driver - ("ORPHAN", "or", "40;31;01"), // symlink to nonexistent file, or non-stat'able file - ("MISSING", "mi", "00"), // ... and the files they point to - ("SETUID", "su", "37;41"), // file that is setuid (u+s) - ("SETGID", "sg", "30;43"), // file that is setgid (g+s) - ("CAPABILITY", "ca", "00"), // file with capability - ("STICKY_OTHER_WRITABLE", "tw", "30;42"), // dir that is sticky and other-writable (+t,o+w) - ("OTHER_WRITABLE", "ow", "34;42"), // dir that is other-writable (o+w) and not sticky - ("STICKY", "st", "37;44"), // dir with the sticky bit set (+t) and not other-writable - ("EXEC", "ex", "01;32"), // files with execute permission - ] -}); +/// Below are the color init strings for the basic file types. +/// One can use codes for 256 or more colors supported by modern terminals. +/// The default color codes use the capabilities of an 8 color terminal +/// with some additional attributes as per the following codes: +/// Attribute codes: +/// 00=none 01=bold 04=underscore 05=blink 07=reverse 08=concealed +/// Text color codes: +/// 30=black 31=red 32=green 33=yellow 34=blue 35=magenta 36=cyan 37=white +/// Background color codes: +/// 40=black 41=red 42=green 43=yellow 44=blue 45=magenta 46=cyan 47=white +/// #NORMAL 00 /// no color code at all +/// #FILE 00 /// regular file: use no color at all +pub static FILE_TYPES: &[(&str, &str, &str)] = &[ + ("RESET", "rs", "0"), // reset to "normal" color + ("DIR", "di", "01;34"), // directory + ("LINK", "ln", "01;36"), // symbolic link + ("MULTIHARDLINK", "mh", "00"), // regular file with more than one link + ("FIFO", "pi", "40;33"), // pipe + ("SOCK", "so", "01;35"), // socket + ("DOOR", "do", "01;35"), // door + ("BLK", "bd", "40;33;01"), // block device driver + ("CHR", "cd", "40;33;01"), // character device driver + ("ORPHAN", "or", "40;31;01"), // symlink to nonexistent file, or non-stat'able file + ("MISSING", "mi", "00"), // ... and the files they point to + ("SETUID", "su", "37;41"), // file that is setuid (u+s) + ("SETGID", "sg", "30;43"), // file that is setgid (g+s) + ("CAPABILITY", "ca", "00"), // file with capability + ("STICKY_OTHER_WRITABLE", "tw", "30;42"), // dir that is sticky and other-writable (+t,o+w) + ("OTHER_WRITABLE", "ow", "34;42"), // dir that is other-writable (o+w) and not sticky + ("STICKY", "st", "37;44"), // dir with the sticky bit set (+t) and not other-writable + ("EXEC", "ex", "01;32"), // files with execute permission +]; /// Colors for file types /// /// List any file extensions like '.gz' or '.tar' that you would like ls /// to color below. Put the extension, a space, and the color init string. /// (and any comments you want to add after a '#') -pub static FILE_COLORS: Lazy> = Lazy::new(|| { - vec![ - /* - // Executables (Windows) - (".cmd", "01;32"), - (".exe", "01;32"), - (".com", "01;32"), - (".btm", "01;32"), - (".bat", "01;32"), - (".sh", "01;32"), - (".csh", "01;32"),*/ - // Archives or compressed - (".tar", "01;31"), - (".tgz", "01;31"), - (".arc", "01;31"), - (".arj", "01;31"), - (".taz", "01;31"), - (".lha", "01;31"), - (".lz4", "01;31"), - (".lzh", "01;31"), - (".lzma", "01;31"), - (".tlz", "01;31"), - (".txz", "01;31"), - (".tzo", "01;31"), - (".t7z", "01;31"), - (".zip", "01;31"), - (".z", "01;31"), - (".dz", "01;31"), - (".gz", "01;31"), - (".lrz", "01;31"), - (".lz", "01;31"), - (".lzo", "01;31"), - (".xz", "01;31"), - (".zst", "01;31"), - (".tzst", "01;31"), - (".bz2", "01;31"), - (".bz", "01;31"), - (".tbz", "01;31"), - (".tbz2", "01;31"), - (".tz", "01;31"), - (".deb", "01;31"), - (".rpm", "01;31"), - (".jar", "01;31"), - (".war", "01;31"), - (".ear", "01;31"), - (".sar", "01;31"), - (".rar", "01;31"), - (".alz", "01;31"), - (".ace", "01;31"), - (".zoo", "01;31"), - (".cpio", "01;31"), - (".7z", "01;31"), - (".rz", "01;31"), - (".cab", "01;31"), - (".wim", "01;31"), - (".swm", "01;31"), - (".dwm", "01;31"), - (".esd", "01;31"), - // Image formats - (".avif", "01;35"), - (".jpg", "01;35"), - (".jpeg", "01;35"), - (".mjpg", "01;35"), - (".mjpeg", "01;35"), - (".gif", "01;35"), - (".bmp", "01;35"), - (".pbm", "01;35"), - (".pgm", "01;35"), - (".ppm", "01;35"), - (".tga", "01;35"), - (".xbm", "01;35"), - (".xpm", "01;35"), - (".tif", "01;35"), - (".tiff", "01;35"), - (".png", "01;35"), - (".svg", "01;35"), - (".svgz", "01;35"), - (".mng", "01;35"), - (".pcx", "01;35"), - (".mov", "01;35"), - (".mpg", "01;35"), - (".mpeg", "01;35"), - (".m2v", "01;35"), - (".mkv", "01;35"), - (".webm", "01;35"), - (".webp", "01;35"), - (".ogm", "01;35"), - (".mp4", "01;35"), - (".m4v", "01;35"), - (".mp4v", "01;35"), - (".vob", "01;35"), - (".qt", "01;35"), - (".nuv", "01;35"), - (".wmv", "01;35"), - (".asf", "01;35"), - (".rm", "01;35"), - (".rmvb", "01;35"), - (".flc", "01;35"), - (".avi", "01;35"), - (".fli", "01;35"), - (".flv", "01;35"), - (".gl", "01;35"), - (".dl", "01;35"), - (".xcf", "01;35"), - (".xwd", "01;35"), - (".yuv", "01;35"), - (".cgm", "01;35"), - (".emf", "01;35"), - // https://wiki.xiph.org/MIME_Types_and_File_Extensions - (".ogv", "01;35"), - (".ogx", "01;35"), - // Audio formats - (".aac", "00;36"), - (".au", "00;36"), - (".flac", "00;36"), - (".m4a", "00;36"), - (".mid", "00;36"), - (".midi", "00;36"), - (".mka", "00;36"), - (".mp3", "00;36"), - (".mpc", "00;36"), - (".ogg", "00;36"), - (".ra", "00;36"), - (".wav", "00;36"), - // https://wiki.xiph.org/MIME_Types_and_File_Extensions - (".oga", "00;36"), - (".opus", "00;36"), - (".spx", "00;36"), - (".xspf", "00;36"), - // Backup files - ("~", "00;90"), - ("#", "00;90"), - (".bak", "00;90"), - (".old", "00;90"), - (".orig", "00;90"), - (".part", "00;90"), - (".rej", "00;90"), - (".swp", "00;90"), - (".tmp", "00;90"), - (".dpkg-dist", "00;90"), - (".dpkg-old", "00;90"), - (".ucf-dist", "00;90"), - (".ucf-new", "00;90"), - (".ucf-old", "00;90"), - (".rpmnew", "00;90"), - (".rpmorig", "00;90"), - (".rpmsave", "00;90"), - ] -}); +pub static FILE_COLORS: &[(&str, &str)] = &[ + /* + // Executables (Windows) + (".cmd", "01;32"), + (".exe", "01;32"), + (".com", "01;32"), + (".btm", "01;32"), + (".bat", "01;32"), + (".sh", "01;32"), + (".csh", "01;32"),*/ + // Archives or compressed + (".tar", "01;31"), + (".tgz", "01;31"), + (".arc", "01;31"), + (".arj", "01;31"), + (".taz", "01;31"), + (".lha", "01;31"), + (".lz4", "01;31"), + (".lzh", "01;31"), + (".lzma", "01;31"), + (".tlz", "01;31"), + (".txz", "01;31"), + (".tzo", "01;31"), + (".t7z", "01;31"), + (".zip", "01;31"), + (".z", "01;31"), + (".dz", "01;31"), + (".gz", "01;31"), + (".lrz", "01;31"), + (".lz", "01;31"), + (".lzo", "01;31"), + (".xz", "01;31"), + (".zst", "01;31"), + (".tzst", "01;31"), + (".bz2", "01;31"), + (".bz", "01;31"), + (".tbz", "01;31"), + (".tbz2", "01;31"), + (".tz", "01;31"), + (".deb", "01;31"), + (".rpm", "01;31"), + (".jar", "01;31"), + (".war", "01;31"), + (".ear", "01;31"), + (".sar", "01;31"), + (".rar", "01;31"), + (".alz", "01;31"), + (".ace", "01;31"), + (".zoo", "01;31"), + (".cpio", "01;31"), + (".7z", "01;31"), + (".rz", "01;31"), + (".cab", "01;31"), + (".wim", "01;31"), + (".swm", "01;31"), + (".dwm", "01;31"), + (".esd", "01;31"), + // Image formats + (".avif", "01;35"), + (".jpg", "01;35"), + (".jpeg", "01;35"), + (".mjpg", "01;35"), + (".mjpeg", "01;35"), + (".gif", "01;35"), + (".bmp", "01;35"), + (".pbm", "01;35"), + (".pgm", "01;35"), + (".ppm", "01;35"), + (".tga", "01;35"), + (".xbm", "01;35"), + (".xpm", "01;35"), + (".tif", "01;35"), + (".tiff", "01;35"), + (".png", "01;35"), + (".svg", "01;35"), + (".svgz", "01;35"), + (".mng", "01;35"), + (".pcx", "01;35"), + (".mov", "01;35"), + (".mpg", "01;35"), + (".mpeg", "01;35"), + (".m2v", "01;35"), + (".mkv", "01;35"), + (".webm", "01;35"), + (".webp", "01;35"), + (".ogm", "01;35"), + (".mp4", "01;35"), + (".m4v", "01;35"), + (".mp4v", "01;35"), + (".vob", "01;35"), + (".qt", "01;35"), + (".nuv", "01;35"), + (".wmv", "01;35"), + (".asf", "01;35"), + (".rm", "01;35"), + (".rmvb", "01;35"), + (".flc", "01;35"), + (".avi", "01;35"), + (".fli", "01;35"), + (".flv", "01;35"), + (".gl", "01;35"), + (".dl", "01;35"), + (".xcf", "01;35"), + (".xwd", "01;35"), + (".yuv", "01;35"), + (".cgm", "01;35"), + (".emf", "01;35"), + // https://wiki.xiph.org/MIME_Types_and_File_Extensions + (".ogv", "01;35"), + (".ogx", "01;35"), + // Audio formats + (".aac", "00;36"), + (".au", "00;36"), + (".flac", "00;36"), + (".m4a", "00;36"), + (".mid", "00;36"), + (".midi", "00;36"), + (".mka", "00;36"), + (".mp3", "00;36"), + (".mpc", "00;36"), + (".ogg", "00;36"), + (".ra", "00;36"), + (".wav", "00;36"), + // https://wiki.xiph.org/MIME_Types_and_File_Extensions + (".oga", "00;36"), + (".opus", "00;36"), + (".spx", "00;36"), + (".xspf", "00;36"), + // Backup files + ("~", "00;90"), + ("#", "00;90"), + (".bak", "00;90"), + (".old", "00;90"), + (".orig", "00;90"), + (".part", "00;90"), + (".rej", "00;90"), + (".swp", "00;90"), + (".tmp", "00;90"), + (".dpkg-dist", "00;90"), + (".dpkg-old", "00;90"), + (".ucf-dist", "00;90"), + (".ucf-new", "00;90"), + (".ucf-old", "00;90"), + (".rpmnew", "00;90"), + (".rpmorig", "00;90"), + (".rpmsave", "00;90"), +]; -pub static FILE_ATTRIBUTE_CODES: Lazy> = Lazy::new(|| { - vec![ - ("normal", "no"), - ("norm", "no"), - ("file", "fi"), - ("reset", "rs"), - ("dir", "di"), - ("lnk", "ln"), - ("link", "ln"), - ("symlink", "ln"), - ("orphan", "or"), - ("missing", "mi"), - ("fifo", "pi"), - ("pipe", "pi"), - ("sock", "so"), - ("blk", "bd"), - ("block", "bd"), - ("chr", "cd"), - ("char", "cd"), - ("door", "do"), - ("exec", "ex"), - ("left", "lc"), - ("leftcode", "lc"), - ("right", "rc"), - ("rightcode", "rc"), - ("end", "ec"), - ("endcode", "ec"), - ("suid", "su"), - ("setuid", "su"), - ("sgid", "sg"), - ("setgid", "sg"), - ("sticky", "st"), - ("other_writable", "ow"), - ("owr", "ow"), - ("sticky_other_writable", "tw"), - ("owt", "tw"), - ("capability", "ca"), - ("multihardlink", "mh"), - ("clrtoeol", "cl"), - ] -}); +pub static FILE_ATTRIBUTE_CODES: &[(&str, &str)] = &[ + ("normal", "no"), + ("norm", "no"), + ("file", "fi"), + ("reset", "rs"), + ("dir", "di"), + ("lnk", "ln"), + ("link", "ln"), + ("symlink", "ln"), + ("orphan", "or"), + ("missing", "mi"), + ("fifo", "pi"), + ("pipe", "pi"), + ("sock", "so"), + ("blk", "bd"), + ("block", "bd"), + ("chr", "cd"), + ("char", "cd"), + ("door", "do"), + ("exec", "ex"), + ("left", "lc"), + ("leftcode", "lc"), + ("right", "rc"), + ("rightcode", "rc"), + ("end", "ec"), + ("endcode", "ec"), + ("suid", "su"), + ("setuid", "su"), + ("sgid", "sg"), + ("setgid", "sg"), + ("sticky", "st"), + ("other_writable", "ow"), + ("owr", "ow"), + ("sticky_other_writable", "tw"), + ("owt", "tw"), + ("capability", "ca"), + ("multihardlink", "mh"), + ("clrtoeol", "cl"), +]; From 1c9413e185c8eb16175761b929a373578f0607e3 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Mon, 4 Dec 2023 22:51:08 +0100 Subject: [PATCH 0456/2851] bring back the old format --- src/uucore/src/lib/features/colors.rs | 4 ++-- tests/fixtures/dircolors/internal.expected | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/uucore/src/lib/features/colors.rs b/src/uucore/src/lib/features/colors.rs index 81d2fd3f336..e0de8b1e3e3 100644 --- a/src/uucore/src/lib/features/colors.rs +++ b/src/uucore/src/lib/features/colors.rs @@ -204,8 +204,8 @@ pub static FILE_COLORS: &[(&str, &str)] = &[ (".spx", "00;36"), (".xspf", "00;36"), // Backup files - ("~", "00;90"), - ("#", "00;90"), + ("*~", "00;90"), + ("*#", "00;90"), (".bak", "00;90"), (".old", "00;90"), (".orig", "00;90"), diff --git a/tests/fixtures/dircolors/internal.expected b/tests/fixtures/dircolors/internal.expected index 933e70bc470..e151973f200 100644 --- a/tests/fixtures/dircolors/internal.expected +++ b/tests/fixtures/dircolors/internal.expected @@ -176,8 +176,8 @@ EXEC 01;32 .opus 00;36 .spx 00;36 .xspf 00;36 -~ 00;90 -# 00;90 +*~ 00;90 +*# 00;90 .bak 00;90 .old 00;90 .orig 00;90 From b0fdb1edef1a197c95faf83ffbb325190dfee9c3 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Mon, 4 Dec 2023 23:25:13 +0100 Subject: [PATCH 0457/2851] Rest of the comments --- src/uu/dircolors/src/dircolors.rs | 158 +++++++++--------------------- tests/by-util/test_dircolors.rs | 2 + 2 files changed, 48 insertions(+), 112 deletions(-) diff --git a/src/uu/dircolors/src/dircolors.rs b/src/uu/dircolors/src/dircolors.rs index cf8ed62922c..41e640b2a5f 100644 --- a/src/uu/dircolors/src/dircolors.rs +++ b/src/uu/dircolors/src/dircolors.rs @@ -7,9 +7,8 @@ use std::borrow::Borrow; use std::env; -use std::fmt::Write; use std::fs::File; -use std::io::IsTerminal; +//use std::io::IsTerminal; use std::io::{BufRead, BufReader}; use std::path::Path; @@ -99,16 +98,20 @@ fn generate_ls_colors(fmt: &OutputFmt, sep: &str) -> String { let mut display_parts = vec![]; let type_output = generate_type_output(fmt); display_parts.push(type_output); - for &(extension, code) in FILE_COLORS.iter() { - display_parts.push(format!("\x1b[{}m*{}\t{}\x1b[0m", code, extension, code)); + for &(extension, code) in FILE_COLORS { + let prefix = if extension.starts_with('*') { "" } else { "*" }; + let formatted_extension = + format!("\x1b[{}m{}{}\t{}\x1b[0m", code, prefix, extension, code); + display_parts.push(formatted_extension); } display_parts.join("\n") } _ => { // existing logic for other formats let mut parts = vec![]; - for &(extension, code) in FILE_COLORS.iter() { - let formatted_extension = format!("*{}", extension); + for &(extension, code) in FILE_COLORS { + let prefix = if extension.starts_with('*') { "" } else { "*" }; + let formatted_extension = format!("{}{}", prefix, extension); parts.push(format!("{}={}", formatted_extension, code)); } let (prefix, suffix) = get_colors_format_strings(fmt); @@ -193,6 +196,9 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let result; if files.is_empty() { + println!("{}", generate_ls_colors(&out_format, ":")); + return Ok(()); + /* // Check if data is being piped into the program if std::io::stdin().is_terminal() { // No data piped, use default behavior @@ -203,6 +209,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let fin = BufReader::new(std::io::stdin()); result = parse(fin.lines().map_while(Result::ok), &out_format, "-"); } + */ } else if files.len() > 1 { return Err(UUsageError::new( 1, @@ -474,117 +481,44 @@ fn escape(s: &str) -> String { pub fn generate_dircolors_config() -> String { let mut config = String::new(); - // Adding the complete header comments as in the original file - writeln!( - config, - "# Configuration file for dircolors, a utility to help you set the" - ) - .unwrap(); - writeln!( - config, - "# LS_COLORS environment variable used by GNU ls with the --color option." - ) - .unwrap(); - writeln!( - config, - "# The keywords COLOR, OPTIONS, and EIGHTBIT (honored by the" - ) - .unwrap(); - writeln!( - config, - "# slackware version of dircolors) are recognized but ignored." - ) - .unwrap(); - writeln!( - config, - "# Global config options can be specified before TERM or COLORTERM entries" - ) - .unwrap(); - writeln!( - config, - "# Below are TERM or COLORTERM entries, which can be glob patterns, which" - ) - .unwrap(); - writeln!( - config, - "# restrict following config to systems with matching environment variables." - ) - .unwrap(); - writeln!(config, "COLORTERM ?*").unwrap(); - for term in TERMS.iter() { - writeln!(config, "TERM {}", term).unwrap(); + config.push_str("# Configuration file for dircolors, a utility to help you set the\n"); + config.push_str("# LS_COLORS environment variable used by GNU ls with the --color option.\n"); + config.push_str("# The keywords COLOR, OPTIONS, and EIGHTBIT (honored by the\n"); + config.push_str("# slackware version of dircolors) are recognized but ignored.\n"); + config.push_str("# Global config options can be specified before TERM or COLORTERM entries\n"); + config.push_str("# Below are TERM or COLORTERM entries, which can be glob patterns, which\n"); + config + .push_str("# restrict following config to systems with matching environment variables.\n"); + config.push_str("COLORTERM ?*\n"); + for term in TERMS { + config.push_str(&format!("TERM {}\n", term)); } - // Adding file types and their color codes with header - writeln!( - config, - "# Below are the color init strings for the basic file types." - ) - .unwrap(); - writeln!( - config, - "# One can use codes for 256 or more colors supported by modern terminals." - ) - .unwrap(); - writeln!( - config, - "# The default color codes use the capabilities of an 8 color terminal" - ) - .unwrap(); - writeln!( - config, - "# with some additional attributes as per the following codes:" - ) - .unwrap(); - writeln!(config, "# Attribute codes:").unwrap(); - writeln!( - config, - "# 00=none 01=bold 04=underscore 05=blink 07=reverse 08=concealed" - ) - .unwrap(); - writeln!(config, "# Text color codes:").unwrap(); - writeln!( - config, - "# 30=black 31=red 32=green 33=yellow 34=blue 35=magenta 36=cyan 37=white" - ) - .unwrap(); - writeln!(config, "# Background color codes:").unwrap(); - writeln!( - config, - "# 40=black 41=red 42=green 43=yellow 44=blue 45=magenta 46=cyan 47=white" - ) - .unwrap(); - writeln!(config, "#NORMAL 00 # no color code at all").unwrap(); - writeln!(config, "#FILE 00 # regular file: use no color at all").unwrap(); - - for (name, _, code) in FILE_TYPES.iter() { - writeln!(config, "{} {}", name, code).unwrap(); + config.push_str("# Below are the color init strings for the basic file types.\n"); + config.push_str("# One can use codes for 256 or more colors supported by modern terminals.\n"); + config.push_str("# The default color codes use the capabilities of an 8 color terminal\n"); + config.push_str("# with some additional attributes as per the following codes:\n"); + config.push_str("# Attribute codes:\n"); + config.push_str("# 00=none 01=bold 04=underscore 05=blink 07=reverse 08=concealed\n"); + config.push_str("# Text color codes:\n"); + config.push_str("# 30=black 31=red 32=green 33=yellow 34=blue 35=magenta 36=cyan 37=white\n"); + config.push_str("# Background color codes:\n"); + config.push_str("# 40=black 41=red 42=green 43=yellow 44=blue 45=magenta 46=cyan 47=white\n"); + config.push_str("#NORMAL 00 # no color code at all\n"); + config.push_str("#FILE 00 # regular file: use no color at all\n"); + + for (name, _, code) in FILE_TYPES { + config.push_str(&format!("{} {}\n", name, code)); } - writeln!( - config, - "# List any file extensions like '.gz' or '.tar' that you would like ls" - ) - .unwrap(); - writeln!( - config, - "# to color below. Put the extension, a space, and the color init string." - ) - .unwrap(); - - for (ext, color) in FILE_COLORS.iter() { - writeln!(config, "{} {}", ext, color).unwrap(); + config.push_str("# List any file extensions like '.gz' or '.tar' that you would like ls\n"); + config.push_str("# to color below. Put the extension, a space, and the color init string.\n"); + + for (ext, color) in FILE_COLORS { + config.push_str(&format!("{} {}\n", ext, color)); } - writeln!( - config, - "# Subsequent TERM or COLORTERM entries, can be used to add / override" - ) - .unwrap(); - write!( - config, - "# config specific to those matching environment variables." - ) - .unwrap(); + config.push_str("# Subsequent TERM or COLORTERM entries, can be used to add / override\n"); + config.push_str("# config specific to those matching environment variables."); config } diff --git a/tests/by-util/test_dircolors.rs b/tests/by-util/test_dircolors.rs index e3752fcde98..4a256352c76 100644 --- a/tests/by-util/test_dircolors.rs +++ b/tests/by-util/test_dircolors.rs @@ -159,6 +159,7 @@ fn test_quoting() { .no_stderr(); } +/* #[test] fn test_print_ls_colors() { new_ucmd!() @@ -168,6 +169,7 @@ fn test_print_ls_colors() { .stdout_is("\x1B[40;33mtw\t40;33\x1B[0m\n") .no_stderr(); } +*/ #[test] fn test_extra_operand() { From 4d2ae8485cd65429e64606119acac3156158ea2b Mon Sep 17 00:00:00 2001 From: Arpit Bhadauria Date: Mon, 4 Dec 2023 22:44:18 +0000 Subject: [PATCH 0458/2851] impl from trait instead of into --- src/uu/expr/src/syntax_tree.rs | 43 ++++++++++++++++------------------ 1 file changed, 20 insertions(+), 23 deletions(-) diff --git a/src/uu/expr/src/syntax_tree.rs b/src/uu/expr/src/syntax_tree.rs index 7677b5e7e54..ae2a44e5205 100644 --- a/src/uu/expr/src/syntax_tree.rs +++ b/src/uu/expr/src/syntax_tree.rs @@ -89,8 +89,8 @@ impl RelationOp { impl NumericOp { fn eval(&self, left: &AstNode, right: &AstNode) -> ExprResult { - let a = >>::into(left.eval()?)?; - let b = >>::into(right.eval()?)?; + let a = ExprResult::::from(left.eval()?)?; + let b = ExprResult::::from(right.eval()?)?; Ok(NumOrStr::Num(match self { Self::Add => a + b, Self::Sub => a - b, @@ -218,29 +218,29 @@ impl From for NumOrStr { } } -impl Into> for NumOrStr { - fn into(self) -> Option { - match self.into() { +impl From for Option { + fn from(s: NumOrStr) -> Self { + match s.into() { Ok(num) => num.to_usize(), Err(_) => None, } } } -impl Into for NumOrStr { - fn into(self) -> String { - match self { - Self::Num(num) => num.to_string(), - Self::Str(str) => str.to_string(), +impl From for String { + fn from(s: NumOrStr) -> Self { + match s { + NumOrStr::Num(num) => num.to_string(), + NumOrStr::Str(str) => str.to_string(), } } } -impl Into> for NumOrStr { - fn into(self) -> ExprResult { - match self { - Self::Num(num) => Ok(num), - Self::Str(str) => str +impl From for ExprResult { + fn from(s: NumOrStr) -> Self { + match s { + NumOrStr::Num(num) => Ok(num), + NumOrStr::Str(str) => str .parse::() .map_err(|_| ExprError::NonIntegerArgument), } @@ -303,9 +303,8 @@ impl AstNode { // // So we coerce errors into 0 to make that the only case we // have to care about. - let pos: usize = >>::into(pos.eval()?).unwrap_or(0); - let length: usize = - >>::into(length.eval()?).unwrap_or(0); + let pos: usize = Option::::from(pos.eval()?).unwrap_or(0); + let length: usize = Option::::from(length.eval()?).unwrap_or(0); let (Some(pos), Some(_)) = (pos.checked_sub(1), length.checked_sub(1)) else { return Ok(NumOrStr::from(String::new())); @@ -315,11 +314,9 @@ impl AstNode { string.chars().skip(pos).take(length).collect::(), )) } - Self::Length { string } => Ok(NumOrStr::from( - >::into(string.eval()?) - .chars() - .count(), - )), + Self::Length { string } => { + Ok(NumOrStr::from(String::from(string.eval()?).chars().count())) + } } } } From 2fcfec6490f87af117a03ccd77ebf487192d548c Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Tue, 5 Dec 2023 21:50:38 +0100 Subject: [PATCH 0459/2851] fuzz seq --- .github/workflows/fuzzing.yml | 1 + fuzz/Cargo.toml | 8 +++- fuzz/fuzz_targets/fuzz_seq.rs | 78 +++++++++++++++++++++++++++++++++++ 3 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 fuzz/fuzz_targets/fuzz_seq.rs diff --git a/.github/workflows/fuzzing.yml b/.github/workflows/fuzzing.yml index 623a03f65c7..eed2ae2fc99 100644 --- a/.github/workflows/fuzzing.yml +++ b/.github/workflows/fuzzing.yml @@ -47,6 +47,7 @@ jobs: - { name: fuzz_expr, should_pass: true } - { name: fuzz_printf, should_pass: false } - { name: fuzz_echo, should_pass: false } + - { name: fuzz_seq, should_pass: false } - { name: fuzz_parse_glob, should_pass: true } - { name: fuzz_parse_size, should_pass: true } - { name: fuzz_parse_time, should_pass: true } diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index b27f5b58677..c98f105ad19 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -18,7 +18,7 @@ uu_test = { path = "../src/uu/test/" } uu_expr = { path = "../src/uu/expr/" } uu_printf = { path = "../src/uu/printf/" } uu_echo = { path = "../src/uu/echo/" } - +uu_seq = { path = "../src/uu/seq/" } # Prevent this from interfering with workspaces [workspace] @@ -42,6 +42,12 @@ path = "fuzz_targets/fuzz_echo.rs" test = false doc = false +[[bin]] +name = "fuzz_seq" +path = "fuzz_targets/fuzz_seq.rs" +test = false +doc = false + [[bin]] name = "fuzz_expr" path = "fuzz_targets/fuzz_expr.rs" diff --git a/fuzz/fuzz_targets/fuzz_seq.rs b/fuzz/fuzz_targets/fuzz_seq.rs new file mode 100644 index 00000000000..2e4ff1a4662 --- /dev/null +++ b/fuzz/fuzz_targets/fuzz_seq.rs @@ -0,0 +1,78 @@ +// This file is part of the uutils coreutils package. +// +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. +// spell-checker:ignore parens + +#![no_main] +use libfuzzer_sys::fuzz_target; +use uu_seq::uumain; + +use rand::Rng; +use std::ffi::OsString; + +mod fuzz_common; +use crate::fuzz_common::CommandResult; +use crate::fuzz_common::{ + compare_result, generate_and_run_uumain, generate_random_string, run_gnu_cmd, +}; +static CMD_PATH: &str = "seq"; + +fn generate_seq() -> String { + let mut rng = rand::thread_rng(); + + // Generate 1 to 3 numbers for seq arguments + let arg_count = rng.gen_range(1..=3); + let mut args = Vec::new(); + + for _ in 0..arg_count { + if rng.gen_ratio(1, 100) { + // 1% chance to add a random string + args.push(generate_random_string(rng.gen_range(1..=10))); + } else { + // 99% chance to add a numeric value + match rng.gen_range(0..=3) { + 0 => args.push(rng.gen_range(-10000..=10000).to_string()), // Large or small integers + 1 => args.push(rng.gen_range(-100.0..100.0).to_string()), // Floating-point numbers + 2 => args.push(rng.gen_range(-100..0).to_string()), // Negative integers + _ => args.push(rng.gen_range(1..=100).to_string()), // Regular integers + } + } + } + + args.join(" ") +} + +fuzz_target!(|_data: &[u8]| { + let seq = generate_seq(); + let mut args = vec![OsString::from("seq")]; + args.extend(seq.split_whitespace().map(OsString::from)); + + let rust_result = generate_and_run_uumain(&args, uumain); + + let gnu_result = match run_gnu_cmd(CMD_PATH, &args[1..], false) { + Ok(result) => result, + Err(error_result) => { + eprintln!("Failed to run GNU command:"); + eprintln!("Stderr: {}", error_result.stderr); + eprintln!("Exit Code: {}", error_result.exit_code); + CommandResult { + stdout: String::new(), + stderr: error_result.stderr, + exit_code: error_result.exit_code, + } + } + }; + + compare_result( + "seq", + &format!("{:?}", &args[1..]), + &rust_result.stdout, + &gnu_result.stdout, + &rust_result.stderr, + &gnu_result.stderr, + rust_result.exit_code, + gnu_result.exit_code, + false, // Set to true if you want to fail on stderr diff + ); +}); From 97ec99cf28d7b699e7d89793bad93f7ca0976053 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Wed, 6 Dec 2023 21:14:59 +0100 Subject: [PATCH 0460/2851] use a single push_str Co-authored-by: Terts Diepraam --- src/uu/dircolors/src/dircolors.rs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/uu/dircolors/src/dircolors.rs b/src/uu/dircolors/src/dircolors.rs index 41e640b2a5f..91544dc7353 100644 --- a/src/uu/dircolors/src/dircolors.rs +++ b/src/uu/dircolors/src/dircolors.rs @@ -481,14 +481,17 @@ fn escape(s: &str) -> String { pub fn generate_dircolors_config() -> String { let mut config = String::new(); - config.push_str("# Configuration file for dircolors, a utility to help you set the\n"); - config.push_str("# LS_COLORS environment variable used by GNU ls with the --color option.\n"); - config.push_str("# The keywords COLOR, OPTIONS, and EIGHTBIT (honored by the\n"); - config.push_str("# slackware version of dircolors) are recognized but ignored.\n"); - config.push_str("# Global config options can be specified before TERM or COLORTERM entries\n"); - config.push_str("# Below are TERM or COLORTERM entries, which can be glob patterns, which\n"); - config - .push_str("# restrict following config to systems with matching environment variables.\n"); + config.push_str( + "\ + # Configuration file for dircolors, a utility to help you set the\n\ + # LS_COLORS environment variable used by GNU ls with the --color option.\n\ + # The keywords COLOR, OPTIONS, and EIGHTBIT (honored by the\n\ + # slackware version of dircolors) are recognized but ignored.\n\ + # Global config options can be specified before TERM or COLORTERM entries\n\ + # Below are TERM or COLORTERM entries, which can be glob patterns, which\n\ + # restrict following config to systems with matching environment variables.\n\ + ", + ); config.push_str("COLORTERM ?*\n"); for term in TERMS { config.push_str(&format!("TERM {}\n", term)); From bd667efa7b1daf9a38edc5272c3bdab8e5743f56 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Wed, 6 Dec 2023 21:15:43 +0100 Subject: [PATCH 0461/2851] simplify the declaration Co-authored-by: Terts Diepraam --- src/uu/dircolors/src/dircolors.rs | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/src/uu/dircolors/src/dircolors.rs b/src/uu/dircolors/src/dircolors.rs index 91544dc7353..fa523451b29 100644 --- a/src/uu/dircolors/src/dircolors.rs +++ b/src/uu/dircolors/src/dircolors.rs @@ -497,18 +497,22 @@ pub fn generate_dircolors_config() -> String { config.push_str(&format!("TERM {}\n", term)); } - config.push_str("# Below are the color init strings for the basic file types.\n"); - config.push_str("# One can use codes for 256 or more colors supported by modern terminals.\n"); - config.push_str("# The default color codes use the capabilities of an 8 color terminal\n"); - config.push_str("# with some additional attributes as per the following codes:\n"); - config.push_str("# Attribute codes:\n"); - config.push_str("# 00=none 01=bold 04=underscore 05=blink 07=reverse 08=concealed\n"); - config.push_str("# Text color codes:\n"); - config.push_str("# 30=black 31=red 32=green 33=yellow 34=blue 35=magenta 36=cyan 37=white\n"); - config.push_str("# Background color codes:\n"); - config.push_str("# 40=black 41=red 42=green 43=yellow 44=blue 45=magenta 46=cyan 47=white\n"); - config.push_str("#NORMAL 00 # no color code at all\n"); - config.push_str("#FILE 00 # regular file: use no color at all\n"); + config.push_str( + "\ + # Below are the color init strings for the basic file types.\n\ + # One can use codes for 256 or more colors supported by modern terminals.\n\ + # The default color codes use the capabilities of an 8 color terminal\n\ + # with some additional attributes as per the following codes:\n\ + # Attribute codes:\n\ + # 00=none 01=bold 04=underscore 05=blink 07=reverse 08=concealed\n\ + # Text color codes:\n\ + # 30=black 31=red 32=green 33=yellow 34=blue 35=magenta 36=cyan 37=white\n\ + # Background color codes:\n\ + # 40=black 41=red 42=green 43=yellow 44=blue 45=magenta 46=cyan 47=white\n\ + #NORMAL 00 # no color code at all\n\ + #FILE 00 # regular file: use no color at all\n\ + ", + ); for (name, _, code) in FILE_TYPES { config.push_str(&format!("{} {}\n", name, code)); From f99987bb35aade972e187ad3b7df6b039f5b72c2 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Wed, 6 Dec 2023 21:19:59 +0100 Subject: [PATCH 0462/2851] fix rustfmt --- src/uu/dircolors/src/dircolors.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/dircolors/src/dircolors.rs b/src/uu/dircolors/src/dircolors.rs index fa523451b29..ecca9d16033 100644 --- a/src/uu/dircolors/src/dircolors.rs +++ b/src/uu/dircolors/src/dircolors.rs @@ -481,7 +481,7 @@ fn escape(s: &str) -> String { pub fn generate_dircolors_config() -> String { let mut config = String::new(); - config.push_str( + config.push_str( "\ # Configuration file for dircolors, a utility to help you set the\n\ # LS_COLORS environment variable used by GNU ls with the --color option.\n\ From 5a32ab8004304fe5434bf42e1faf066a58b95736 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Wed, 6 Dec 2023 15:35:38 +0100 Subject: [PATCH 0463/2851] ls: implement --hyperlink --- Cargo.lock | 1 + Cargo.toml | 1 + src/uu/hostname/Cargo.toml | 2 +- src/uu/ls/Cargo.toml | 1 + src/uu/ls/src/ls.rs | 50 ++++++++++++++++++++++++++++++++++++-- tests/by-util/test_ls.rs | 30 +++++++++++++++++++++++ 6 files changed, 82 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bf638b421a1..5060da44116 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2618,6 +2618,7 @@ dependencies = [ "chrono", "clap", "glob", + "hostname", "lscolors", "number_prefix", "once_cell", diff --git a/Cargo.toml b/Cargo.toml index 14e700ee0a6..13b99700851 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -284,6 +284,7 @@ fundu = "2.0.0" gcd = "2.3" glob = "0.3.1" half = "2.3" +hostname = "0.3" indicatif = "0.17" itertools = "0.12.0" libc = "0.2.150" diff --git a/src/uu/hostname/Cargo.toml b/src/uu/hostname/Cargo.toml index a9b033d123b..1fe10170964 100644 --- a/src/uu/hostname/Cargo.toml +++ b/src/uu/hostname/Cargo.toml @@ -16,7 +16,7 @@ path = "src/hostname.rs" [dependencies] clap = { workspace = true } -hostname = { version = "0.3", features = ["set"] } +hostname = { workspace = true, features = ["set"] } uucore = { workspace = true, features = ["wide"] } [target.'cfg(target_os = "windows")'.dependencies] diff --git a/src/uu/ls/Cargo.toml b/src/uu/ls/Cargo.toml index 96cf7df1a0d..a82a1f37e07 100644 --- a/src/uu/ls/Cargo.toml +++ b/src/uu/ls/Cargo.toml @@ -31,6 +31,7 @@ uucore = { workspace = true, features = [ ] } once_cell = { workspace = true } selinux = { workspace = true, optional = true } +hostname = { workspace = true } [[bin]] name = "ls" diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index c6b10677c6e..deb8aac3dfe 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -155,6 +155,7 @@ pub mod options { pub static GROUP_DIRECTORIES_FIRST: &str = "group-directories-first"; pub static ZERO: &str = "zero"; pub static DIRED: &str = "dired"; + pub static HYPERLINK: &str = "hyperlink"; } const DEFAULT_TERM_WIDTH: u16 = 80; @@ -418,6 +419,7 @@ pub struct Config { group_directories_first: bool, line_ending: LineEnding, dired: bool, + hyperlink: bool, } // Fields that can be removed or added to the long format @@ -566,6 +568,25 @@ fn extract_color(options: &clap::ArgMatches) -> bool { } } +/// Extracts the hyperlink option to use based on the options provided. +/// +/// # Returns +/// +/// A boolean representing whether to hyperlink files. +fn extract_hyperlink(options: &clap::ArgMatches) -> bool { + let hyperlink = options + .get_one::(options::HYPERLINK) + .unwrap() + .as_str(); + + match hyperlink { + "always" | "yes" | "force" => true, + "auto" | "tty" | "if-tty" => std::io::stdout().is_terminal(), + "never" | "no" | "none" => false, + _ => unreachable!("should be handled by clap"), + } +} + /// Extracts the quoting style to use based on the options provided. /// /// # Arguments @@ -736,10 +757,9 @@ impl Config { } let sort = extract_sort(options); - let time = extract_time(options); - let mut needs_color = extract_color(options); + let hyperlink = extract_hyperlink(options); let opt_block_size = options.get_one::(options::size::BLOCK_SIZE); let opt_si = opt_block_size.is_some() @@ -1020,6 +1040,7 @@ impl Config { group_directories_first: options.get_flag(options::GROUP_DIRECTORIES_FIRST), line_ending: LineEnding::from_zero_flag(options.get_flag(options::ZERO)), dired, + hyperlink, }) } } @@ -1154,6 +1175,19 @@ pub fn uu_app() -> Command { .help("generate output designed for Emacs' dired (Directory Editor) mode") .action(ArgAction::SetTrue), ) + .arg( + Arg::new(options::HYPERLINK) + .long(options::HYPERLINK) + .help("hyperlink file names WHEN") + .value_parser([ + "always", "yes", "force", "auto", "tty", "if-tty", "never", "no", "none", + ]) + .require_equals(true) + .num_args(0..=1) + .default_missing_value("always") + .default_value("never") + .value_name("WHEN"), + ) // The next four arguments do not override with the other format // options, see the comment in Config::from for the reason. // Ideally, they would use Arg::override_with, with their own name @@ -2959,6 +2993,18 @@ fn display_file_name( // infer it because the color codes mess up term_grid's width calculation. let mut width = name.width(); + if config.hyperlink { + let hostname = hostname::get().unwrap_or(OsString::from("")); + let hostname = hostname.to_string_lossy(); + + let absolute_path = fs::canonicalize(&path.p_buf).unwrap_or_default(); + let absolute_path = absolute_path.to_string_lossy(); + + // TODO encode path + // \x1b = ESC, \x07 = BEL + name = format!("\x1b]8;;file://{hostname}{absolute_path}\x07{name}\x1b]8;;\x07"); + } + if let Some(ls_colors) = &config.color { let md = path.md(out); name = if md.is_some() { diff --git a/tests/by-util/test_ls.rs b/tests/by-util/test_ls.rs index fcd57170d48..8bc2b75ac77 100644 --- a/tests/by-util/test_ls.rs +++ b/tests/by-util/test_ls.rs @@ -3855,3 +3855,33 @@ fn test_posixly_correct() { .succeeds() .stdout_contains_line("total 8"); } + +#[test] +fn test_ls_hyperlink() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + let file = "a.txt"; + + at.touch(file); + + let path = at.root_dir_resolved(); + let separator = std::path::MAIN_SEPARATOR_STR; + + let result = scene.ucmd().arg("--hyperlink").succeeds(); + assert!(result.stdout_str().contains("\x1b]8;;file://")); + assert!(result + .stdout_str() + .contains(&format!("{path}{separator}{file}\x07{file}\x1b]8;;\x07"))); + + let result = scene.ucmd().arg("--hyperlink=always").succeeds(); + assert!(result.stdout_str().contains("\x1b]8;;file://")); + assert!(result + .stdout_str() + .contains(&format!("{path}{separator}{file}\x07{file}\x1b]8;;\x07"))); + + scene + .ucmd() + .arg("--hyperlink=never") + .succeeds() + .stdout_is(format!("{file}\n")); +} From 09999427ccfb822918fc6969e16d0fc656087ff1 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Thu, 7 Dec 2023 10:02:29 +0100 Subject: [PATCH 0464/2851] du: merge imports --- src/uu/du/src/du.rs | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/src/uu/du/src/du.rs b/src/uu/du/src/du.rs index dc03a64f218..989e663e46c 100644 --- a/src/uu/du/src/du.rs +++ b/src/uu/du/src/du.rs @@ -3,35 +3,30 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -use chrono::prelude::DateTime; -use chrono::Local; -use clap::ArgAction; -use clap::{crate_version, Arg, ArgMatches, Command}; +use chrono::{DateTime, Local}; +use clap::{crate_version, Arg, ArgAction, ArgMatches, Command}; use glob::Pattern; use std::collections::HashSet; use std::env; -use std::fs; -use std::fs::File; +use std::error::Error; +use std::fmt::Display; #[cfg(not(windows))] use std::fs::Metadata; -use std::io::BufRead; -use std::io::BufReader; +use std::fs::{self, File}; +use std::io::{BufRead, BufReader}; #[cfg(not(windows))] use std::os::unix::fs::MetadataExt; #[cfg(windows)] use std::os::windows::fs::MetadataExt; #[cfg(windows)] use std::os::windows::io::AsRawHandle; -use std::path::Path; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use std::str::FromStr; use std::sync::mpsc; use std::thread; use std::time::{Duration, UNIX_EPOCH}; -use std::{error::Error, fmt::Display}; use uucore::display::{print_verbatim, Quotable}; -use uucore::error::FromIo; -use uucore::error::{UError, UResult, USimpleError}; +use uucore::error::{FromIo, UError, UResult, USimpleError}; use uucore::line_ending::LineEnding; use uucore::parse_glob; use uucore::parse_size::{parse_size_u64, ParseSizeError}; From 6cae19156926ef0ca68e1e3657dd51e2066d4e8b Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Thu, 7 Dec 2023 11:06:04 +0100 Subject: [PATCH 0465/2851] du: remove ArgMatches from StatPrinter --- src/uu/du/src/du.rs | 165 ++++++++++++++++++++++---------------------- 1 file changed, 84 insertions(+), 81 deletions(-) diff --git a/src/uu/du/src/du.rs b/src/uu/du/src/du.rs index 989e663e46c..26e697abb46 100644 --- a/src/uu/du/src/du.rs +++ b/src/uu/du/src/du.rs @@ -87,6 +87,18 @@ struct Options { count_links: bool, inodes: bool, verbose: bool, + threshold: Option, + apparent_size: bool, + // TODO: the size conversion fields should be unified + si: bool, + bytes: bool, + human_readable: bool, + block_size_1k: bool, + block_size_1m: bool, + block_size: u64, + time: Option