diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 3cf9b969c..c59c6d185 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -2139,8 +2139,11 @@ fn sort_entries(entries: &mut [PathData], config: &Config, out: &mut BufWriter entries.sort_by(|a, b| a.display_name.cmp(&b.display_name)), Sort::Version => entries.sort_by(|a, b| { - version_cmp(&a.p_buf.to_string_lossy(), &b.p_buf.to_string_lossy()) - .then(a.p_buf.to_string_lossy().cmp(&b.p_buf.to_string_lossy())) + version_cmp( + os_str_as_bytes_lossy(a.p_buf.as_os_str()).as_ref(), + os_str_as_bytes_lossy(b.p_buf.as_os_str()).as_ref(), + ) + .then(a.p_buf.to_string_lossy().cmp(&b.p_buf.to_string_lossy())) }), Sort::Extension => entries.sort_by(|a, b| { a.p_buf diff --git a/src/uu/sort/src/check.rs b/src/uu/sort/src/check.rs index 699ecae3d..dbf598574 100644 --- a/src/uu/sort/src/check.rs +++ b/src/uu/sort/src/check.rs @@ -72,7 +72,7 @@ pub fn check(path: &OsStr, settings: &GlobalSettings) -> UResult<()> { return Err(SortError::Disorder { file: path.to_owned(), line_number: line_idx, - line: new_first.line.to_owned(), + line: String::from_utf8_lossy(new_first.line).into_owned(), silent: settings.check_silent, } .into()); @@ -86,7 +86,7 @@ pub fn check(path: &OsStr, settings: &GlobalSettings) -> UResult<()> { return Err(SortError::Disorder { file: path.to_owned(), line_number: line_idx, - line: b.line.to_owned(), + line: String::from_utf8_lossy(b.line).into_owned(), silent: settings.check_silent, } .into()); diff --git a/src/uu/sort/src/chunks.rs b/src/uu/sort/src/chunks.rs index 4b8ad4785..5ac330c18 100644 --- a/src/uu/sort/src/chunks.rs +++ b/src/uu/sort/src/chunks.rs @@ -17,9 +17,7 @@ use memchr::memchr_iter; use self_cell::self_cell; use uucore::error::{UResult, USimpleError}; -use crate::{ - GeneralBigDecimalParseResult, GlobalSettings, Line, SortError, numeric_str_cmp::NumInfo, -}; +use crate::{GeneralBigDecimalParseResult, GlobalSettings, Line, numeric_str_cmp::NumInfo}; self_cell!( /// The chunk that is passed around between threads. @@ -41,7 +39,7 @@ pub struct ChunkContents<'a> { #[derive(Debug)] pub struct LineData<'a> { - pub selections: Vec<&'a str>, + pub selections: Vec<&'a [u8]>, pub num_infos: Vec, pub parsed_floats: Vec, pub line_num_floats: Vec>, @@ -68,7 +66,7 @@ impl Chunk { let selections = unsafe { // SAFETY: (same as above) It is safe to (temporarily) transmute to a vector of &str with a longer lifetime, // because the vector is empty. - std::mem::transmute::, Vec<&'static str>>(std::mem::take( + std::mem::transmute::, Vec<&'static [u8]>>(std::mem::take( &mut contents.line_data.selections, )) }; @@ -100,7 +98,7 @@ impl Chunk { pub struct RecycledChunk { lines: Vec>, - selections: Vec<&'static str>, + selections: Vec<&'static [u8]>, num_infos: Vec, parsed_floats: Vec, line_num_floats: Vec>, @@ -180,15 +178,14 @@ pub fn read( let selections = unsafe { // SAFETY: It is safe to transmute to an empty vector of selections with shorter lifetime. // It was only temporarily transmuted to a Vec> to make recycling possible. - std::mem::transmute::, Vec<&'_ str>>(selections) + std::mem::transmute::, Vec<&'_ [u8]>>(selections) }; let mut lines = unsafe { // SAFETY: (same as above) It is safe to transmute to a vector of lines with shorter lifetime, // because it was only temporarily transmuted to a Vec> to make recycling possible. std::mem::transmute::>, Vec>>(lines) }; - let read = std::str::from_utf8(&buffer[..read]) - .map_err(|error| SortError::Uft8Error { error })?; + let read = &buffer[..read]; let mut line_data = LineData { selections, num_infos, @@ -205,13 +202,13 @@ pub fn read( /// Split `read` into `Line`s, and add them to `lines`. fn parse_lines<'a>( - read: &'a str, + read: &'a [u8], lines: &mut Vec>, line_data: &mut LineData<'a>, separator: u8, settings: &GlobalSettings, ) { - let read = read.strip_suffix(separator as char).unwrap_or(read); + let read = read.strip_suffix(&[separator]).unwrap_or(read); assert!(lines.is_empty()); assert!(line_data.selections.is_empty()); @@ -220,7 +217,7 @@ fn parse_lines<'a>( assert!(line_data.line_num_floats.is_empty()); let mut token_buffer = vec![]; lines.extend( - read.split(separator as char) + read.split(|&c| c == separator) .enumerate() .map(|(index, line)| Line::create(line, index, line_data, &mut token_buffer, settings)), ); diff --git a/src/uu/sort/src/custom_str_cmp.rs b/src/uu/sort/src/custom_str_cmp.rs index fb128d9af..aa4f73ea7 100644 --- a/src/uu/sort/src/custom_str_cmp.rs +++ b/src/uu/sort/src/custom_str_cmp.rs @@ -9,7 +9,7 @@ use std::cmp::Ordering; -fn filter_char(c: char, ignore_non_printing: bool, ignore_non_dictionary: bool) -> bool { +fn filter_char(c: u8, ignore_non_printing: bool, ignore_non_dictionary: bool) -> bool { if ignore_non_dictionary && !(c.is_ascii_alphanumeric() || c.is_ascii_whitespace()) { return false; } @@ -19,7 +19,7 @@ fn filter_char(c: char, ignore_non_printing: bool, ignore_non_dictionary: bool) true } -fn cmp_chars(a: char, b: char, ignore_case: bool) -> Ordering { +fn cmp_chars(a: u8, b: u8, ignore_case: bool) -> Ordering { if ignore_case { a.to_ascii_uppercase().cmp(&b.to_ascii_uppercase()) } else { @@ -28,8 +28,8 @@ fn cmp_chars(a: char, b: char, ignore_case: bool) -> Ordering { } pub fn custom_str_cmp( - a: &str, - b: &str, + a: &[u8], + b: &[u8], ignore_non_printing: bool, ignore_non_dictionary: bool, ignore_case: bool, @@ -39,11 +39,11 @@ pub fn custom_str_cmp( return a.cmp(b); } let mut a_chars = a - .chars() - .filter(|&c| filter_char(c, ignore_non_printing, ignore_non_dictionary)); + .iter() + .filter(|&&c| filter_char(c, ignore_non_printing, ignore_non_dictionary)); let mut b_chars = b - .chars() - .filter(|&c| filter_char(c, ignore_non_printing, ignore_non_dictionary)); + .iter() + .filter(|&&c| filter_char(c, ignore_non_printing, ignore_non_dictionary)); loop { let a_char = a_chars.next(); let b_char = b_chars.next(); @@ -52,7 +52,7 @@ pub fn custom_str_cmp( (Some(_), None) => return Ordering::Greater, (None, Some(_)) => return Ordering::Less, (Some(a_char), Some(b_char)) => { - let ordering = cmp_chars(a_char, b_char, ignore_case); + let ordering = cmp_chars(*a_char, *b_char, ignore_case); if ordering != Ordering::Equal { return ordering; } diff --git a/src/uu/sort/src/ext_sort.rs b/src/uu/sort/src/ext_sort.rs index 7a65fea5b..4c003a3f2 100644 --- a/src/uu/sort/src/ext_sort.rs +++ b/src/uu/sort/src/ext_sort.rs @@ -272,7 +272,7 @@ fn write( fn write_lines(lines: &[Line], writer: &mut T, separator: u8) { for s in lines { - writer.write_all(s.line.as_bytes()).unwrap(); + writer.write_all(s.line).unwrap(); writer.write_all(&[separator]).unwrap(); } } diff --git a/src/uu/sort/src/numeric_str_cmp.rs b/src/uu/sort/src/numeric_str_cmp.rs index 4484d21c1..40530cc51 100644 --- a/src/uu/sort/src/numeric_str_cmp.rs +++ b/src/uu/sort/src/numeric_str_cmp.rs @@ -28,8 +28,8 @@ pub struct NumInfo { #[derive(Debug, PartialEq, Eq, Clone)] pub struct NumInfoParseSettings { pub accept_si_units: bool, - pub thousands_separator: Option, - pub decimal_pt: Option, + pub thousands_separator: Option, + pub decimal_pt: Option, } impl Default for NumInfoParseSettings { @@ -37,7 +37,7 @@ impl Default for NumInfoParseSettings { Self { accept_si_units: false, thousands_separator: None, - decimal_pt: Some('.'), + decimal_pt: Some(b'.'), } } } @@ -51,7 +51,7 @@ impl NumInfo { /// If the input is not a number (which has to be treated as zero), the returned empty range /// will be 0..0. #[allow(clippy::cognitive_complexity)] - pub fn parse(num: &str, parse_settings: &NumInfoParseSettings) -> (Self, Range) { + pub fn parse(num: &[u8], parse_settings: &NumInfoParseSettings) -> (Self, Range) { let mut exponent = -1; let mut had_decimal_pt = false; let mut had_digit = false; @@ -60,12 +60,12 @@ impl NumInfo { let mut first_char = true; - for (idx, char) in num.char_indices() { - if first_char && char.is_whitespace() { + for (idx, &char) in num.iter().enumerate() { + if first_char && char.is_ascii_whitespace() { continue; } - if first_char && char == '-' { + if first_char && char == b'-' { sign = Sign::Negative; first_char = false; continue; @@ -84,7 +84,16 @@ impl NumInfo { let has_si_unit = parse_settings.accept_si_units && matches!( char, - 'K' | 'k' | 'M' | 'G' | 'T' | 'P' | 'E' | 'Z' | 'Y' | 'R' | 'Q' + b'K' | b'k' + | b'M' + | b'G' + | b'T' + | b'P' + | b'E' + | b'Z' + | b'Y' + | b'R' + | b'Q' ); ( Self { exponent, sign }, @@ -112,7 +121,7 @@ impl NumInfo { continue; } had_digit = true; - if start.is_none() && char == '0' { + if start.is_none() && char == b'0' { if had_decimal_pt { // We're parsing a number whose first nonzero digit is after the decimal point. exponent -= 1; @@ -124,7 +133,7 @@ impl NumInfo { if !had_decimal_pt { exponent += 1; } - if start.is_none() && char != '0' { + if start.is_none() && char != b'0' { start = Some(idx); } } @@ -150,7 +159,7 @@ impl NumInfo { } fn is_invalid_char( - c: char, + c: u8, had_decimal_pt: &mut bool, parse_settings: &NumInfoParseSettings, ) -> bool { @@ -168,19 +177,19 @@ impl NumInfo { } } -fn get_unit(unit: Option) -> u8 { +fn get_unit(unit: Option) -> u8 { if let Some(unit) = unit { match unit { - 'K' | 'k' => 1, - 'M' => 2, - 'G' => 3, - 'T' => 4, - 'P' => 5, - 'E' => 6, - 'Z' => 7, - 'Y' => 8, - 'R' => 9, - 'Q' => 10, + b'K' | b'k' => 1, + b'M' => 2, + b'G' => 3, + b'T' => 4, + b'P' => 5, + b'E' => 6, + b'Z' => 7, + b'Y' => 8, + b'R' => 9, + b'Q' => 10, _ => 0, } } else { @@ -191,16 +200,16 @@ fn get_unit(unit: Option) -> u8 { /// Compare two numbers according to the rules of human numeric comparison. /// The SI-Unit takes precedence over the actual value (i.e. 2000M < 1G). pub fn human_numeric_str_cmp( - (a, a_info): (&str, &NumInfo), - (b, b_info): (&str, &NumInfo), + (a, a_info): (&[u8], &NumInfo), + (b, b_info): (&[u8], &NumInfo), ) -> Ordering { // 1. Sign if a_info.sign != b_info.sign { return a_info.sign.cmp(&b_info.sign); } // 2. Unit - let a_unit = get_unit(a.chars().next_back()); - let b_unit = get_unit(b.chars().next_back()); + let a_unit = get_unit(a.iter().next_back().copied()); + let b_unit = get_unit(b.iter().next_back().copied()); let ordering = a_unit.cmp(&b_unit); if ordering == Ordering::Equal { // 3. Number @@ -215,7 +224,7 @@ pub fn human_numeric_str_cmp( /// Compare two numbers as strings without parsing them as a number first. This should be more performant and can handle numbers more precisely. /// [`NumInfo`] is needed to provide a fast path for most numbers. #[inline(always)] -pub fn numeric_str_cmp((a, a_info): (&str, &NumInfo), (b, b_info): (&str, &NumInfo)) -> Ordering { +pub fn numeric_str_cmp((a, a_info): (&[u8], &NumInfo), (b, b_info): (&[u8], &NumInfo)) -> Ordering { // check for a difference in the sign if a_info.sign != b_info.sign { return a_info.sign.cmp(&b_info.sign); @@ -226,22 +235,22 @@ pub fn numeric_str_cmp((a, a_info): (&str, &NumInfo), (b, b_info): (&str, &NumIn a_info.exponent.cmp(&b_info.exponent) } else { // walk the characters from the front until we find a difference - let mut a_chars = a.chars().filter(char::is_ascii_digit); - let mut b_chars = b.chars().filter(char::is_ascii_digit); + let mut a_chars = a.iter().copied().filter(u8::is_ascii_digit); + let mut b_chars = b.iter().copied().filter(u8::is_ascii_digit); loop { let a_next = a_chars.next(); let b_next = b_chars.next(); match (a_next, b_next) { (None, None) => break Ordering::Equal, (Some(c), None) => { - break if c == '0' && a_chars.all(|c| c == '0') { + break if c == b'0' && a_chars.all(|c| c == b'0') { Ordering::Equal } else { Ordering::Greater }; } (None, Some(c)) => { - break if c == '0' && b_chars.all(|c| c == '0') { + break if c == b'0' && b_chars.all(|c| c == b'0') { Ordering::Equal } else { Ordering::Less @@ -270,7 +279,7 @@ mod tests { #[test] fn parses_exp() { - let n = "1"; + let n = b"1"; assert_eq!( NumInfo::parse(n, &NumInfoParseSettings::default()), ( @@ -281,7 +290,7 @@ mod tests { 0..1 ) ); - let n = "100"; + let n = b"100"; assert_eq!( NumInfo::parse(n, &NumInfoParseSettings::default()), ( @@ -292,12 +301,12 @@ mod tests { 0..3 ) ); - let n = "1,000"; + let n = b"1,000"; assert_eq!( NumInfo::parse( n, &NumInfoParseSettings { - thousands_separator: Some(','), + thousands_separator: Some(b','), ..Default::default() } ), @@ -309,7 +318,7 @@ mod tests { 0..5 ) ); - let n = "1,000"; + let n = b"1,000"; assert_eq!( NumInfo::parse(n, &NumInfoParseSettings::default()), ( @@ -320,7 +329,7 @@ mod tests { 0..1 ) ); - let n = "1000.00"; + let n = b"1000.00"; assert_eq!( NumInfo::parse(n, &NumInfoParseSettings::default()), ( @@ -334,7 +343,7 @@ mod tests { } #[test] fn parses_negative_exp() { - let n = "0.00005"; + let n = b"0.00005"; assert_eq!( NumInfo::parse(n, &NumInfoParseSettings::default()), ( @@ -345,7 +354,7 @@ mod tests { 6..7 ) ); - let n = "00000.00005"; + let n = b"00000.00005"; assert_eq!( NumInfo::parse(n, &NumInfoParseSettings::default()), ( @@ -360,7 +369,7 @@ mod tests { #[test] fn parses_sign() { - let n = "5"; + let n = b"5"; assert_eq!( NumInfo::parse(n, &NumInfoParseSettings::default()), ( @@ -371,7 +380,7 @@ mod tests { 0..1 ) ); - let n = "-5"; + let n = b"-5"; assert_eq!( NumInfo::parse(n, &NumInfoParseSettings::default()), ( @@ -382,7 +391,7 @@ mod tests { 1..2 ) ); - let n = " -5"; + let n = b" -5"; assert_eq!( NumInfo::parse(n, &NumInfoParseSettings::default()), ( @@ -395,7 +404,7 @@ mod tests { ); } - fn test_helper(a: &str, b: &str, expected: Ordering) { + fn test_helper(a: &[u8], b: &[u8], expected: Ordering) { 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( @@ -408,71 +417,71 @@ mod tests { } #[test] fn test_single_digit() { - test_helper("1", "2", Ordering::Less); - test_helper("0", "0", Ordering::Equal); + test_helper(b"1", b"2", Ordering::Less); + test_helper(b"0", b"0", Ordering::Equal); } #[test] fn test_minus() { - test_helper("-1", "-2", Ordering::Greater); - test_helper("-0", "-0", Ordering::Equal); + test_helper(b"-1", b"-2", Ordering::Greater); + test_helper(b"-0", b"-0", Ordering::Equal); } #[test] fn test_different_len() { - test_helper("-20", "-100", Ordering::Greater); - test_helper("10.0", "2.000000", Ordering::Greater); + test_helper(b"-20", b"-100", Ordering::Greater); + test_helper(b"10.0", b"2.000000", Ordering::Greater); } #[test] fn test_decimal_digits() { - test_helper("20.1", "20.2", Ordering::Less); - test_helper("20.1", "20.15", Ordering::Less); - test_helper("-20.1", "+20.15", Ordering::Less); - test_helper("-20.1", "-20", Ordering::Less); + test_helper(b"20.1", b"20.2", Ordering::Less); + test_helper(b"20.1", b"20.15", Ordering::Less); + test_helper(b"-20.1", b"+20.15", Ordering::Less); + test_helper(b"-20.1", b"-20", Ordering::Less); } #[test] fn test_trailing_zeroes() { - test_helper("20.00000", "20.1", Ordering::Less); - test_helper("20.00000", "20.0", Ordering::Equal); + test_helper(b"20.00000", b"20.1", Ordering::Less); + test_helper(b"20.00000", b"20.0", Ordering::Equal); } #[test] fn test_invalid_digits() { - test_helper("foo", "bar", Ordering::Equal); - test_helper("20.1", "a", Ordering::Greater); - test_helper("-20.1", "a", Ordering::Less); - test_helper("a", "0.15", Ordering::Less); + test_helper(b"foo", b"bar", Ordering::Equal); + test_helper(b"20.1", b"a", Ordering::Greater); + test_helper(b"-20.1", b"a", Ordering::Less); + test_helper(b"a", b"0.15", Ordering::Less); } #[test] fn test_multiple_decimal_pts() { - test_helper("10.0.0", "50.0.0", Ordering::Less); - test_helper("0.1.", "0.2.0", Ordering::Less); - test_helper("1.1.", "0", Ordering::Greater); - test_helper("1.1.", "-0", Ordering::Greater); + test_helper(b"10.0.0", b"50.0.0", Ordering::Less); + test_helper(b"0.1.", b"0.2.0", Ordering::Less); + test_helper(b"1.1.", b"0", Ordering::Greater); + test_helper(b"1.1.", b"-0", Ordering::Greater); } #[test] fn test_leading_decimal_pts() { - test_helper(".0", ".0", Ordering::Equal); - test_helper(".1", ".0", Ordering::Greater); - test_helper(".02", "0", Ordering::Greater); + test_helper(b".0", b".0", Ordering::Equal); + test_helper(b".1", b".0", Ordering::Greater); + test_helper(b".02", b"0", Ordering::Greater); } #[test] fn test_leading_zeroes() { - test_helper("000000.0", ".0", Ordering::Equal); - test_helper("0.1", "0000000000000.0", Ordering::Greater); - test_helper("-01", "-2", Ordering::Greater); + test_helper(b"000000.0", b".0", Ordering::Equal); + test_helper(b"0.1", b"0000000000000.0", Ordering::Greater); + test_helper(b"-01", b"-2", Ordering::Greater); } #[test] fn minus_zero() { // This matches GNU sort behavior. - test_helper("-0", "0", Ordering::Equal); - test_helper("-0x", "0", Ordering::Equal); + test_helper(b"-0", b"0", Ordering::Equal); + test_helper(b"-0x", b"0", Ordering::Equal); } #[test] fn double_minus() { - test_helper("--1", "0", Ordering::Equal); + test_helper(b"--1", b"0", Ordering::Equal); } #[test] fn single_minus() { - let info = NumInfo::parse("-", &NumInfoParseSettings::default()); + let info = NumInfo::parse(b"-", &NumInfoParseSettings::default()); assert_eq!( info, ( @@ -487,7 +496,7 @@ mod tests { #[test] fn invalid_with_unit() { let info = NumInfo::parse( - "-K", + b"-K", &NumInfoParseSettings { accept_si_units: true, ..Default::default() diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index 5c97abba3..069b8b310 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -42,7 +42,6 @@ use std::path::Path; use std::path::PathBuf; use std::str::Utf8Error; use thiserror::Error; -use unicode_width::UnicodeWidthStr; use uucore::display::Quotable; use uucore::error::{FromIo, strip_errno}; use uucore::error::{UError, UResult, USimpleError, UUsageError, set_exit_code}; @@ -57,7 +56,6 @@ use uucore::{format_usage, show_error}; use crate::tmp_dir::TmpDirWrapper; use uucore::locale::{get_message, get_message_with_args}; - mod options { pub mod modes { pub const SORT: &str = "sort"; @@ -112,10 +110,10 @@ mod options { pub const FILES: &str = "files"; } -const DECIMAL_PT: char = '.'; +const DECIMAL_PT: u8 = b'.'; -const NEGATIVE: char = '-'; -const POSITIVE: char = '+'; +const NEGATIVE: &u8 = &b'-'; +const POSITIVE: &u8 = &b'+'; // Choosing a higher buffer size does not result in performance improvements // (at least not on my machine). TODO: In the future, we should also take the amount of @@ -232,9 +230,9 @@ pub struct Output { } impl Output { - fn new(name: Option<&OsStr>) -> UResult { + fn new(name: Option>) -> UResult { let file = if let Some(name) = name { - let path = Path::new(name); + let path = Path::new(name.as_ref()); // This is different from `File::create()` because we don't truncate the output yet. // This allows using the output file as an input file. #[allow(clippy::suspicious_open_options)] @@ -246,7 +244,7 @@ impl Output { path: path.to_owned(), error: e, })?; - Some((name.to_os_string(), file)) + Some((name.as_ref().to_owned(), file)) } else { None }; @@ -288,7 +286,7 @@ pub struct GlobalSettings { check_silent: bool, salt: Option<[u8; 16]>, selectors: Vec, - separator: Option, + separator: Option, threads: String, line_ending: LineEnding, buffer_size: usize, @@ -481,15 +479,15 @@ impl Default for KeySettings { } enum Selection<'a> { AsBigDecimal(GeneralBigDecimalParseResult), - WithNumInfo(&'a str, NumInfo), - Str(&'a str), + WithNumInfo(&'a [u8], NumInfo), + Str(&'a [u8]), } type Field = Range; #[derive(Clone, Debug)] pub struct Line<'a> { - line: &'a str, + line: &'a [u8], index: usize, } @@ -499,7 +497,7 @@ impl<'a> Line<'a> { /// If additional data is needed for sorting it is added to `line_data`. /// `token_buffer` allows to reuse the allocation for tokens. fn create( - line: &'a str, + line: &'a [u8], index: usize, line_data: &mut LineData<'a>, token_buffer: &mut Vec, @@ -511,9 +509,10 @@ impl<'a> Line<'a> { } if settings.mode == SortMode::Numeric { // exclude inf, nan, scientific notation - let line_num_float = (!line.contains(char::is_alphabetic)) - .then(|| line.parse::().ok()) - .flatten(); + let line_num_float = (!line.iter().any(u8::is_ascii_alphabetic)) + .then(|| std::str::from_utf8(line).ok()) + .flatten() + .and_then(|s| s.parse::().ok()); line_data.line_num_floats.push(line_num_float); } for (selector, selection) in settings @@ -541,7 +540,7 @@ impl<'a> Line<'a> { if settings.debug { self.print_debug(settings, writer)?; } else { - writer.write_all(self.line.as_bytes())?; + writer.write_all(self.line)?; writer.write_all(&[settings.line_ending.into()])?; } Ok(()) @@ -558,8 +557,15 @@ impl<'a> Line<'a> { // which are not a performance problem in any case. Therefore there aren't any special performance // optimizations here. - let line = self.line.replace('\t', ">"); - writeln!(writer, "{line}")?; + let line = self + .line + .iter() + .copied() + .map(|c| if c == b'\t' { b'>' } else { c }) + .collect::>(); + + writer.write_all(&line)?; + writeln!(writer)?; let mut fields = vec![]; tokenize(self.line, settings.separator, &mut fields); @@ -585,23 +591,26 @@ impl<'a> Line<'a> { // This was not a valid number. // Report no match at the first non-whitespace character. let leading_whitespace = self.line[selection.clone()] - .find(|c: char| !c.is_whitespace()) + .iter() + .position(|c| !c.is_ascii_whitespace()) .unwrap_or(0); selection.start += leading_whitespace; selection.end += leading_whitespace; } else { // include a trailing si unit - if selector.settings.mode == SortMode::HumanNumeric - && self.line[selection.end..initial_selection.end].starts_with( - &['k', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y', 'R', 'Q'][..], - ) - { - selection.end += 1; + if selector.settings.mode == SortMode::HumanNumeric { + if let Some( + b'k' | b'K' | b'M' | b'G' | b'T' | b'P' | b'E' | b'Z' | b'Y' | b'R' + | b'Q', + ) = self.line[selection.end..initial_selection.end].first() + { + selection.end += 1; + } } // include leading zeroes, a leading minus or a leading decimal point - while self.line[initial_selection.start..selection.start] - .ends_with(&['-', '0', '.'][..]) + while let Some(b'-' | b'0' | b'.') = + self.line[initial_selection.start..selection.start].last() { selection.start -= 1; } @@ -620,8 +629,9 @@ impl<'a> Line<'a> { let initial_selection = &self.line[selection.clone()]; let mut month_chars = initial_selection - .char_indices() - .skip_while(|(_, c)| c.is_whitespace()); + .iter() + .enumerate() + .skip_while(|(_, c)| c.is_ascii_whitespace()); let month = if month_parse(initial_selection) == Month::Unknown { // We failed to parse a month, which is equivalent to matching nothing. @@ -646,20 +656,14 @@ impl<'a> Line<'a> { _ => {} } - write!( - writer, - "{}", - " ".repeat(UnicodeWidthStr::width(&line[..selection.start])) - )?; + let select = &line[..selection.start]; + write!(writer, "{}", " ".repeat(select.len()))?; if selection.is_empty() { writeln!(writer, "{}", get_message("sort-error-no-match-for-key"))?; } else { - writeln!( - writer, - "{}", - "_".repeat(UnicodeWidthStr::width(&line[selection])) - )?; + let select = &line[selection]; + writeln!(writer, "{}", "_".repeat(select.len()))?; } } @@ -680,11 +684,7 @@ impl<'a> Line<'a> { if self.line.is_empty() { writeln!(writer, "{}", get_message("sort-error-no-match-for-key"))?; } else { - writeln!( - writer, - "{}", - "_".repeat(UnicodeWidthStr::width(line.as_str())) - )?; + writeln!(writer, "{}", "_".repeat(self.line.len()))?; } } Ok(()) @@ -692,7 +692,7 @@ impl<'a> Line<'a> { } /// Tokenize a line into fields. The result is stored into `token_buffer`. -fn tokenize(line: &str, separator: Option, token_buffer: &mut Vec) { +fn tokenize(line: &[u8], separator: Option, token_buffer: &mut Vec) { assert!(token_buffer.is_empty()); if let Some(separator) = separator { tokenize_with_separator(line, separator, token_buffer); @@ -704,12 +704,12 @@ fn tokenize(line: &str, separator: Option, token_buffer: &mut Vec) /// By default fields are separated by the first whitespace after non-whitespace. /// Whitespace is included in fields at the start. /// The result is stored into `token_buffer`. -fn tokenize_default(line: &str, token_buffer: &mut Vec) { +fn tokenize_default(line: &[u8], token_buffer: &mut Vec) { token_buffer.push(0..0); // pretend that there was whitespace in front of the line let mut previous_was_whitespace = true; - for (idx, char) in line.char_indices() { - if char.is_whitespace() { + for (idx, char) in line.iter().enumerate() { + if char.is_ascii_whitespace() { if !previous_was_whitespace { token_buffer.last_mut().unwrap().end = idx; token_buffer.push(idx..0); @@ -724,10 +724,11 @@ fn tokenize_default(line: &str, token_buffer: &mut Vec) { /// Split between separators. These separators are not included in fields. /// The result is stored into `token_buffer`. -fn tokenize_with_separator(line: &str, separator: char, token_buffer: &mut Vec) { +fn tokenize_with_separator(line: &[u8], separator: u8, token_buffer: &mut Vec) { let separator_indices = line - .char_indices() - .filter_map(|(i, c)| if c == separator { Some(i) } else { None }); + .iter() + .enumerate() + .filter_map(|(i, &c)| if c == separator { Some(i) } else { None }); let mut start = 0; for sep_idx in separator_indices { token_buffer.push(start..sep_idx); @@ -934,38 +935,38 @@ impl FieldSelector { /// Get the selection that corresponds to this selector for the line. /// If `needs_fields` returned false, tokens may be empty. - fn get_selection<'a>(&self, line: &'a str, tokens: &[Field]) -> Selection<'a> { + fn get_selection<'a>(&self, line: &'a [u8], tokens: &[Field]) -> Selection<'a> { // `get_range` expects `None` when we don't need tokens and would get confused by an empty vector. let tokens = if self.needs_tokens { Some(tokens) } else { None }; - let mut range = &line[self.get_range(line, tokens)]; + let mut range_str = &line[self.get_range(line, tokens)]; if self.settings.mode == SortMode::Numeric || self.settings.mode == SortMode::HumanNumeric { // Parse NumInfo for this number. let (info, num_range) = NumInfo::parse( - range, + range_str, &NumInfoParseSettings { accept_si_units: self.settings.mode == SortMode::HumanNumeric, ..Default::default() }, ); // Shorten the range to what we need to pass to numeric_str_cmp later. - range = &range[num_range]; - Selection::WithNumInfo(range, info) + range_str = &range_str[num_range]; + Selection::WithNumInfo(range_str, info) } else if self.settings.mode == SortMode::GeneralNumeric { // Parse this number as BigDecimal, as this is the requirement for general numeric sorting. - Selection::AsBigDecimal(general_bd_parse(&range[get_leading_gen(range)])) + Selection::AsBigDecimal(general_bd_parse(&range_str[get_leading_gen(range_str)])) } else { // This is not a numeric sort, so we don't need a NumCache. - Selection::Str(range) + Selection::Str(range_str) } } /// Look up the range in the line that corresponds to this selector. /// If `needs_fields` returned false, tokens must be None. - fn get_range(&self, line: &str, tokens: Option<&[Field]>) -> Range { + fn get_range(&self, line: &[u8], tokens: Option<&[Field]>) -> Range { enum Resolution { // The start index of the resolved character, inclusive StartOfChar(usize), @@ -980,7 +981,7 @@ impl FieldSelector { /// Get the index for this line given the [`KeyPosition`] fn resolve_index( - line: &str, + line: &[u8], tokens: Option<&[Field]>, position: &KeyPosition, ) -> Resolution { @@ -1004,13 +1005,15 @@ impl FieldSelector { // strip blanks if needed if position.ignore_blanks { idx += line[idx..] - .char_indices() - .find(|(_, c)| !c.is_whitespace()) + .iter() + .enumerate() + .find(|(_, c)| !c.is_ascii_whitespace()) .map_or(line[idx..].len(), |(idx, _)| idx); } // apply the character index idx += line[idx..] - .char_indices() + .iter() + .enumerate() .nth(position.char - 1) .map_or(line[idx..].len(), |(idx, _)| idx); if idx >= line.len() { @@ -1028,7 +1031,7 @@ impl FieldSelector { let mut range = match to { Some(Resolution::StartOfChar(mut to)) => { // We need to include the character at `to`. - to += line[to..].chars().next().map_or(1, char::len_utf8); + to += 1; from..to } Some(Resolution::EndOfChar(to)) => from..to, @@ -1058,17 +1061,16 @@ impl FieldSelector { /// Creates an `Arg` that conflicts with all other sort modes. fn make_sort_mode_arg(mode: &'static str, short: char, help: String) -> Arg { - let mut arg = Arg::new(mode) + Arg::new(mode) .short(short) .long(mode) .help(help) - .action(ArgAction::SetTrue); - for possible_mode in &options::modes::ALL_SORT_MODES { - if *possible_mode != mode { - arg = arg.conflicts_with(possible_mode); - } - } - arg + .action(ArgAction::SetTrue) + .conflicts_with_all( + options::modes::ALL_SORT_MODES + .iter() + .filter(|&&m| m != mode), + ) } #[cfg(target_os = "linux")] @@ -1169,43 +1171,37 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { settings.mode = if matches.get_flag(options::modes::HUMAN_NUMERIC) || matches .get_one::(options::modes::SORT) - .map(|s| s.as_str()) - == Some("human-numeric") + .is_some_and(|s| s == "human-numeric") { SortMode::HumanNumeric } else if matches.get_flag(options::modes::MONTH) || matches .get_one::(options::modes::SORT) - .map(|s| s.as_str()) - == Some("month") + .is_some_and(|s| s == "month") { SortMode::Month } else if matches.get_flag(options::modes::GENERAL_NUMERIC) || matches .get_one::(options::modes::SORT) - .map(|s| s.as_str()) - == Some("general-numeric") + .is_some_and(|s| s == "general-numeric") { SortMode::GeneralNumeric } else if matches.get_flag(options::modes::NUMERIC) || matches .get_one::(options::modes::SORT) - .map(|s| s.as_str()) - == Some("numeric") + .is_some_and(|s| s == "numeric") { SortMode::Numeric } else if matches.get_flag(options::modes::VERSION) || matches .get_one::(options::modes::SORT) - .map(|s| s.as_str()) - == Some("version") + .is_some_and(|s| s == "version") { SortMode::Version } else if matches.get_flag(options::modes::RANDOM) || matches .get_one::(options::modes::SORT) - .map(|s| s.as_str()) - == Some("random") + .is_some_and(|s| s == "random") { settings.salt = Some(get_rand_string()); SortMode::Random @@ -1264,15 +1260,14 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } Err(e) => { let error_message = if *e.kind() == IntErrorKind::PosOverflow { + let batch_too_large = get_message_with_args( + "sort-batch-size-too-large", + HashMap::from([("arg".to_string(), n_merge.quote().to_string())]), + ); + #[cfg(target_os = "linux")] { - show_error!( - "{}", - get_message_with_args( - "sort-batch-size-too-large", - HashMap::from([("arg".to_string(), n_merge.quote().to_string())]) - ) - ); + show_error!("{}", batch_too_large); get_message_with_args( "sort-maximum-batch-size-rlimit", @@ -1281,10 +1276,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } #[cfg(not(target_os = "linux"))] { - get_message_with_args( - "sort-batch-size-too-large", - HashMap::from([("arg".to_string(), n_merge.quote().to_string())]), - ) + batch_too_large } } else { get_message_with_args( @@ -1351,7 +1343,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { // This rejects non-ASCII codepoints, but perhaps we don't have to. // On the other hand GNU accepts any single byte, valid unicode or not. // (Supporting multi-byte chars would require changes in tokenize_with_separator().) - if separator.len() != 1 { + let &[sep_char] = separator.as_bytes() else { return Err(UUsageError::new( 2, get_message_with_args( @@ -1359,8 +1351,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { HashMap::from([("separator".to_string(), separator.quote().to_string())]), ), )); - } - settings.separator = Some(separator.chars().next().unwrap()); + }; + settings.separator = Some(sep_char); } if let Some(values) = matches.get_many::(options::KEY) { @@ -1398,11 +1390,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { open(file)?; } - let output = Output::new( - matches - .get_one::(options::OUTPUT) - .map(|s| s.as_os_str()), - )?; + let output = Output::new(matches.get_one::(options::OUTPUT))?; settings.init_precomputed(); @@ -1811,20 +1799,21 @@ fn compare_by<'a>( // scientific notation, so we strip those lines only after the end of the following numeric string. // For example, 5e10KFD would be 5e10 or 5x10^10 and +10000HFKJFK would become 10000. #[allow(clippy::cognitive_complexity)] -fn get_leading_gen(input: &str) -> Range { - let trimmed = input.trim_start(); - let leading_whitespace_len = input.len() - trimmed.len(); +fn get_leading_gen(inp: &[u8]) -> Range { + let trimmed = inp.trim_ascii_start(); + let leading_whitespace_len = inp.len() - trimmed.len(); // check for inf, -inf and nan - for allowed_prefix in ["inf", "-inf", "nan"] { - if trimmed.is_char_boundary(allowed_prefix.len()) + const ALLOWED_PREFIXES: &[&[u8]] = &[b"inf", b"-inf", b"nan"]; + for &allowed_prefix in ALLOWED_PREFIXES { + if trimmed.len() >= allowed_prefix.len() && trimmed[..allowed_prefix.len()].eq_ignore_ascii_case(allowed_prefix) { return leading_whitespace_len..(leading_whitespace_len + allowed_prefix.len()); } } // Make this iter peekable to see if next char is numeric - let mut char_indices = itertools::peek_nth(trimmed.char_indices()); + let mut char_indices = itertools::peek_nth(trimmed.iter().enumerate()); let first = char_indices.peek(); @@ -1835,13 +1824,13 @@ fn get_leading_gen(input: &str) -> Range { let mut had_e_notation = false; let mut had_decimal_pt = false; let mut had_hex_notation: bool = false; - while let Some((idx, c)) = char_indices.next() { + while let Some((idx, &c)) = char_indices.next() { if had_hex_notation && c.is_ascii_hexdigit() { continue; } if c.is_ascii_digit() { - if c == '0' && matches!(char_indices.peek(), Some((_, 'x' | 'X'))) { + if c == b'0' && matches!(char_indices.peek(), Some((_, b'x' | b'X'))) { had_hex_notation = true; char_indices.next(); } @@ -1852,12 +1841,12 @@ fn get_leading_gen(input: &str) -> Range { had_decimal_pt = true; continue; } - let is_decimal_e = (c == 'e' || c == 'E') && !had_hex_notation; - let is_hex_e = (c == 'p' || c == 'P') && had_hex_notation; + let is_decimal_e = (c == b'e' || c == b'E') && !had_hex_notation; + let is_hex_e = (c == b'p' || c == b'P') && had_hex_notation; if (is_decimal_e || is_hex_e) && !had_e_notation { // we can only consume the 'e' if what follow is either a digit, or a sign followed by a digit. - if let Some(&(_, next_char)) = char_indices.peek() { - if (next_char == '+' || next_char == '-') + if let Some(&(_, &next_char)) = char_indices.peek() { + if (next_char == b'+' || next_char == b'-') && matches!( char_indices.peek_nth(2), Some((_, c)) if c.is_ascii_digit() @@ -1876,7 +1865,7 @@ fn get_leading_gen(input: &str) -> Range { } return leading_whitespace_len..(leading_whitespace_len + idx); } - leading_whitespace_len..input.len() + leading_whitespace_len..inp.len() } #[derive(Clone, PartialEq, PartialOrd, Debug)] @@ -1891,7 +1880,12 @@ pub enum GeneralBigDecimalParseResult { /// Parse the beginning string into a [`GeneralBigDecimalParseResult`]. /// Using a [`GeneralBigDecimalParseResult`] instead of [`ExtendedBigDecimal`] is necessary to correctly order floats. #[inline(always)] -fn general_bd_parse(a: &str) -> GeneralBigDecimalParseResult { +fn general_bd_parse(a: &[u8]) -> GeneralBigDecimalParseResult { + // The string should be valid ASCII to be parsed. + let Ok(a) = std::str::from_utf8(a) else { + return GeneralBigDecimalParseResult::Invalid; + }; + // Parse digits, and fold in recoverable errors let ebd = match ExtendedBigDecimal::extended_parse(a) { Err(ExtendedParserError::NotNumeric) => return GeneralBigDecimalParseResult::Invalid, @@ -1933,7 +1927,7 @@ fn get_hash(t: &T) -> u64 { s.finish() } -fn random_shuffle(a: &str, b: &str, salt: &[u8]) -> Ordering { +fn random_shuffle(a: &[u8], b: &[u8], salt: &[u8]) -> Ordering { let da = get_hash(&(a, salt)); let db = get_hash(&(b, salt)); da.cmp(&db) @@ -1957,47 +1951,31 @@ enum Month { } /// Parse the beginning string into a Month, returning [`Month::Unknown`] on errors. -fn month_parse(line: &str) -> Month { - let line = line.trim(); +fn month_parse(line: &[u8]) -> Month { + let line = line.trim_ascii_start(); - const MONTHS: [(&str, Month); 12] = [ - ("JAN", Month::January), - ("FEB", Month::February), - ("MAR", Month::March), - ("APR", Month::April), - ("MAY", Month::May), - ("JUN", Month::June), - ("JUL", Month::July), - ("AUG", Month::August), - ("SEP", Month::September), - ("OCT", Month::October), - ("NOV", Month::November), - ("DEC", Month::December), - ]; - - for (month_str, month) in &MONTHS { - if line.is_char_boundary(month_str.len()) - && line[..month_str.len()].eq_ignore_ascii_case(month_str) - { - return *month; - } + match line.get(..3).map(|x| x.to_ascii_uppercase()).as_deref() { + Some(b"JAN") => Month::January, + Some(b"FEB") => Month::February, + Some(b"MAR") => Month::March, + Some(b"APR") => Month::April, + Some(b"MAY") => Month::May, + Some(b"JUN") => Month::June, + Some(b"JUL") => Month::July, + Some(b"AUG") => Month::August, + Some(b"SEP") => Month::September, + Some(b"OCT") => Month::October, + Some(b"NOV") => Month::November, + Some(b"DEC") => Month::December, + _ => Month::Unknown, } - - Month::Unknown } -fn month_compare(a: &str, b: &str) -> Ordering { - #![allow(clippy::comparison_chain)] +fn month_compare(a: &[u8], b: &[u8]) -> Ordering { let ma = month_parse(a); let mb = month_parse(b); - if ma > mb { - Ordering::Greater - } else if ma < mb { - Ordering::Less - } else { - Ordering::Equal - } + ma.cmp(&mb) } fn print_sorted<'a, T: Iterator>>( @@ -2094,7 +2072,7 @@ mod tests { use super::*; - fn tokenize_helper(line: &str, separator: Option) -> Vec { + fn tokenize_helper(line: &[u8], separator: Option) -> Vec { let mut buffer = vec![]; tokenize(line, separator, &mut buffer); buffer @@ -2109,8 +2087,8 @@ mod tests { #[test] fn test_random_shuffle() { - let a = "Ted"; - let b = "Ted"; + let a = b"Ted"; + let b = b"Ted"; let c = get_rand_string(); assert_eq!(Ordering::Equal, random_shuffle(a, b, &c)); @@ -2118,23 +2096,23 @@ mod tests { #[test] fn test_month_compare() { - let a = "JaN"; - let b = "OCt"; + let a = b"JaN"; + let b = b"OCt"; assert_eq!(Ordering::Less, month_compare(a, b)); } #[test] fn test_version_compare() { - let a = "1.2.3-alpha2"; - let b = "1.4.0"; + let a = b"1.2.3-alpha2"; + let b = b"1.4.0"; assert_eq!(Ordering::Less, version_cmp(a, b)); } #[test] fn test_random_compare() { - let a = "9"; - let b = "9"; + let a = b"9"; + let b = b"9"; let c = get_rand_string(); assert_eq!(Ordering::Equal, random_shuffle(a, b, &c)); @@ -2142,13 +2120,13 @@ mod tests { #[test] fn test_tokenize_fields() { - let line = "foo bar b x"; + let line = b"foo bar b x"; assert_eq!(tokenize_helper(line, None), vec![0..3, 3..7, 7..9, 9..14]); } #[test] fn test_tokenize_fields_leading_whitespace() { - let line = " foo bar b x"; + let line = b" foo bar b x"; assert_eq!( tokenize_helper(line, None), vec![0..7, 7..11, 11..13, 13..18] @@ -2157,21 +2135,21 @@ mod tests { #[test] fn test_tokenize_fields_custom_separator() { - let line = "aaa foo bar b x"; + let line = b"aaa foo bar b x"; assert_eq!( - tokenize_helper(line, Some('a')), + tokenize_helper(line, Some(b'a')), vec![0..0, 1..1, 2..2, 3..9, 10..18] ); } #[test] fn test_tokenize_fields_trailing_custom_separator() { - let line = "a"; - assert_eq!(tokenize_helper(line, Some('a')), vec![0..0]); - let line = "aa"; - assert_eq!(tokenize_helper(line, Some('a')), vec![0..0, 1..1]); - let line = "..a..a"; - assert_eq!(tokenize_helper(line, Some('a')), vec![0..2, 3..5]); + let line = b"a"; + assert_eq!(tokenize_helper(line, Some(b'a')), vec![0..0]); + let line = b"aa"; + assert_eq!(tokenize_helper(line, Some(b'a')), vec![0..0, 1..1]); + let line = b"..a..a"; + assert_eq!(tokenize_helper(line, Some(b'a')), vec![0..2, 3..5]); } #[test] diff --git a/src/uu/sort/src/tmp_dir.rs b/src/uu/sort/src/tmp_dir.rs index 009eef459..2dedc59ed 100644 --- a/src/uu/sort/src/tmp_dir.rs +++ b/src/uu/sort/src/tmp_dir.rs @@ -60,21 +60,23 @@ impl TmpDirWrapper { // and the program doesn't terminate before the handler has finished let _lock = lock.lock().unwrap(); if let Err(e) = remove_tmp_dir(&path) { - let mut args = HashMap::new(); - args.insert("error".to_string(), e.to_string()); show_error!( "{}", - get_message_with_args("sort-failed-to-delete-temporary-directory", args) + get_message_with_args( + "sort-failed-to-delete-temporary-directory", + HashMap::from([("error".to_string(), e.to_string())]) + ) ); } std::process::exit(2) }) .map_err(|e| { - let mut args = HashMap::new(); - args.insert("error".to_string(), e.to_string()); USimpleError::new( 2, - get_message_with_args("sort-failed-to-set-up-signal-handler", args), + get_message_with_args( + "sort-failed-to-set-up-signal-handler", + HashMap::from([("error".to_string(), e.to_string())]), + ), ) }) } diff --git a/src/uucore/src/lib/features/version_cmp.rs b/src/uucore/src/lib/features/version_cmp.rs index 492313d1b..f3f8a1b61 100644 --- a/src/uucore/src/lib/features/version_cmp.rs +++ b/src/uucore/src/lib/features/version_cmp.rs @@ -10,15 +10,15 @@ use std::cmp::Ordering; /// Compares the non-digit parts of a version. /// Special cases: ~ are before everything else, even ends ("a~" < "a") /// Letters are before non-letters -fn version_non_digit_cmp(a: &str, b: &str) -> Ordering { - let mut a_chars = a.chars(); - let mut b_chars = b.chars(); +fn version_non_digit_cmp(a: &[u8], b: &[u8]) -> Ordering { + let mut a_chars = a.iter(); + let mut b_chars = b.iter(); loop { match (a_chars.next(), b_chars.next()) { (Some(c1), Some(c2)) if c1 == c2 => {} (None, None) => return Ordering::Equal, - (_, Some('~')) => return Ordering::Greater, - (Some('~'), _) => return Ordering::Less, + (_, Some(b'~')) => return Ordering::Greater, + (Some(b'~'), _) => return Ordering::Less, (None, Some(_)) => return Ordering::Less, (Some(_), None) => return Ordering::Greater, (Some(c1), Some(c2)) if c1.is_ascii_alphabetic() && !c2.is_ascii_alphabetic() => { @@ -27,27 +27,27 @@ fn version_non_digit_cmp(a: &str, b: &str) -> Ordering { (Some(c1), Some(c2)) if !c1.is_ascii_alphabetic() && c2.is_ascii_alphabetic() => { return Ordering::Greater; } - (Some(c1), Some(c2)) => return c1.cmp(&c2), + (Some(c1), Some(c2)) => return c1.cmp(c2), } } } /// Remove file endings matching the regex (\.[A-Za-z~][A-Za-z0-9~]*)*$ -fn remove_file_ending(a: &str) -> &str { +fn remove_file_ending(a: &[u8]) -> &[u8] { let mut ending_start = None; let mut prev_was_dot = false; - for (idx, char) in a.char_indices() { - if char == '.' { + for (idx, &char) in a.iter().enumerate() { + if char == b'.' { if ending_start.is_none() || prev_was_dot { ending_start = Some(idx); } prev_was_dot = true; } else if prev_was_dot { prev_was_dot = false; - if !char.is_ascii_alphabetic() && char != '~' { + if !char.is_ascii_alphabetic() && char != b'~' { ending_start = None; } - } else if !char.is_ascii_alphanumeric() && char != '~' { + } else if !char.is_ascii_alphanumeric() && char != b'~' { ending_start = None; } } @@ -62,7 +62,7 @@ fn remove_file_ending(a: &str) -> &str { } /// Compare two version strings. -pub fn version_cmp(mut a: &str, mut b: &str) -> Ordering { +pub fn version_cmp(mut a: &[u8], mut b: &[u8]) -> Ordering { let str_cmp = a.cmp(b); if str_cmp == Ordering::Equal { return str_cmp; @@ -77,21 +77,21 @@ pub fn version_cmp(mut a: &str, mut b: &str) -> Ordering { (false, false) => {} } // 2. Dots - match (a == ".", b == ".") { + match (a == b".", b == b".") { (true, false) => return Ordering::Less, (false, true) => return Ordering::Greater, (true, true) => unreachable!(), (false, false) => {} } // 3. Two Dots - match (a == "..", b == "..") { + match (a == b"..", b == b"..") { (true, false) => return Ordering::Less, (false, true) => return Ordering::Greater, (true, true) => unreachable!(), (false, false) => {} } // 4. Strings starting with a dot - match (a.starts_with('.'), b.starts_with('.')) { + match (a.starts_with(b"."), b.starts_with(b".")) { (true, false) => return Ordering::Less, (false, true) => return Ordering::Greater, (true, true) => { @@ -115,8 +115,8 @@ pub fn version_cmp(mut a: &str, mut b: &str) -> Ordering { // 2. Compare leading numerical part // 3. Repeat while !a.is_empty() || !b.is_empty() { - let a_numerical_start = a.find(|c: char| c.is_ascii_digit()).unwrap_or(a.len()); - let b_numerical_start = b.find(|c: char| c.is_ascii_digit()).unwrap_or(b.len()); + let a_numerical_start = a.iter().position(|c| c.is_ascii_digit()).unwrap_or(a.len()); + let b_numerical_start = b.iter().position(|c| c.is_ascii_digit()).unwrap_or(b.len()); let a_str = &a[..a_numerical_start]; let b_str = &b[..b_numerical_start]; @@ -129,11 +129,17 @@ pub fn version_cmp(mut a: &str, mut b: &str) -> Ordering { a = &a[a_numerical_start..]; b = &b[a_numerical_start..]; - let a_numerical_end = a.find(|c: char| !c.is_ascii_digit()).unwrap_or(a.len()); - let b_numerical_end = b.find(|c: char| !c.is_ascii_digit()).unwrap_or(b.len()); + let a_numerical_end = a + .iter() + .position(|c| !c.is_ascii_digit()) + .unwrap_or(a.len()); + let b_numerical_end = b + .iter() + .position(|c| !c.is_ascii_digit()) + .unwrap_or(b.len()); - let a_str = a[..a_numerical_end].trim_start_matches('0'); - let b_str = b[..b_numerical_end].trim_start_matches('0'); + let a_str = &a[a.iter().position(|&c| c != b'0').unwrap_or(a.len())..a_numerical_end]; + let b_str = &b[b.iter().position(|&c| c != b'0').unwrap_or(b.len())..b_numerical_end]; match a_str.len().cmp(&b_str.len()) { Ordering::Equal => {} @@ -159,138 +165,138 @@ mod tests { #[test] fn test_version_cmp() { // Identical strings - assert_eq!(version_cmp("hello", "hello"), Ordering::Equal); + assert_eq!(version_cmp(b"hello", b"hello"), Ordering::Equal); - assert_eq!(version_cmp("file12", "file12"), Ordering::Equal); + assert_eq!(version_cmp(b"file12", b"file12"), Ordering::Equal); assert_eq!( - version_cmp("file12-suffix", "file12-suffix"), + version_cmp(b"file12-suffix", b"file12-suffix"), Ordering::Equal ); assert_eq!( - version_cmp("file12-suffix24", "file12-suffix24"), + version_cmp(b"file12-suffix24", b"file12-suffix24"), Ordering::Equal ); // Shortened names - assert_eq!(version_cmp("world", "wo"), Ordering::Greater); + assert_eq!(version_cmp(b"world", b"wo"), Ordering::Greater); - assert_eq!(version_cmp("hello10wo", "hello10world"), Ordering::Less); + assert_eq!(version_cmp(b"hello10wo", b"hello10world"), Ordering::Less); // Simple names - assert_eq!(version_cmp("world", "hello"), Ordering::Greater); + assert_eq!(version_cmp(b"world", b"hello"), Ordering::Greater); - assert_eq!(version_cmp("hello", "world"), Ordering::Less); + assert_eq!(version_cmp(b"hello", b"world"), Ordering::Less); - assert_eq!(version_cmp("apple", "ant"), Ordering::Greater); + assert_eq!(version_cmp(b"apple", b"ant"), Ordering::Greater); - assert_eq!(version_cmp("ant", "apple"), Ordering::Less); + assert_eq!(version_cmp(b"ant", b"apple"), Ordering::Less); // Uppercase letters assert_eq!( - version_cmp("Beef", "apple"), + version_cmp(b"Beef", b"apple"), Ordering::Less, "Uppercase letters are sorted before all lowercase letters" ); - assert_eq!(version_cmp("Apple", "apple"), Ordering::Less); + assert_eq!(version_cmp(b"Apple", b"apple"), Ordering::Less); - assert_eq!(version_cmp("apple", "aPple"), Ordering::Greater); + assert_eq!(version_cmp(b"apple", b"aPple"), Ordering::Greater); // Numbers assert_eq!( - version_cmp("100", "20"), + version_cmp(b"100", b"20"), Ordering::Greater, "Greater numbers are greater even if they start with a smaller digit", ); assert_eq!( - version_cmp("20", "20"), + version_cmp(b"20", b"20"), Ordering::Equal, "Equal numbers are equal" ); assert_eq!( - version_cmp("15", "200"), + version_cmp(b"15", b"200"), Ordering::Less, "Small numbers are smaller" ); // Comparing numbers with other characters assert_eq!( - version_cmp("1000", "apple"), + version_cmp(b"1000", b"apple"), Ordering::Less, "Numbers are sorted before other characters" ); assert_eq!( // spell-checker:disable-next-line - version_cmp("file1000", "fileapple"), + version_cmp(b"file1000", b"fileapple"), Ordering::Less, "Numbers in the middle of the name are sorted before other characters" ); // Leading zeroes assert_eq!( - version_cmp("012", "12"), + version_cmp(b"012", b"12"), Ordering::Equal, "A single leading zero does not make a difference" ); assert_eq!( - version_cmp("000800", "0000800"), + version_cmp(b"000800", b"0000800"), Ordering::Equal, "Multiple leading zeros do not make a difference" ); // Numbers and other characters combined - assert_eq!(version_cmp("ab10", "aa11"), Ordering::Greater); + assert_eq!(version_cmp(b"ab10", b"aa11"), Ordering::Greater); assert_eq!( - version_cmp("aa10", "aa11"), + version_cmp(b"aa10", b"aa11"), Ordering::Less, "Numbers after other characters are handled correctly." ); assert_eq!( - version_cmp("aa2", "aa100"), + version_cmp(b"aa2", b"aa100"), Ordering::Less, "Numbers after alphabetical characters are handled correctly." ); assert_eq!( - version_cmp("aa10bb", "aa11aa"), + version_cmp(b"aa10bb", b"aa11aa"), Ordering::Less, "Number is used even if alphabetical characters after it differ." ); assert_eq!( - version_cmp("aa10aa0010", "aa11aa1"), + version_cmp(b"aa10aa0010", b"aa11aa1"), Ordering::Less, "Second number is ignored if the first number differs." ); assert_eq!( - version_cmp("aa10aa0010", "aa10aa1"), + version_cmp(b"aa10aa0010", b"aa10aa1"), Ordering::Greater, "Second number is used if the rest is equal." ); assert_eq!( - version_cmp("aa10aa0010", "aa00010aa1"), + version_cmp(b"aa10aa0010", b"aa00010aa1"), Ordering::Greater, "Second number is used if the rest is equal up to leading zeroes of the first number." ); assert_eq!( - version_cmp("aa10aa0022", "aa010aa022"), + version_cmp(b"aa10aa0022", b"aa010aa022"), Ordering::Equal, "Test multiple numeric values with leading zeros" ); assert_eq!( - version_cmp("file-1.4", "file-1.13"), + version_cmp(b"file-1.4", b"file-1.13"), Ordering::Less, "Periods are handled as normal text, not as a decimal point." ); @@ -299,42 +305,48 @@ mod tests { // u64 == 18446744073709551615 so this should be plenty: // 20000000000000000000000 assert_eq!( - version_cmp("aa2000000000000000000000bb", "aa002000000000000000000001bb"), + version_cmp( + b"aa2000000000000000000000bb", + b"aa002000000000000000000001bb" + ), Ordering::Less, "Numbers larger than u64::MAX are handled correctly without crashing" ); assert_eq!( - version_cmp("aa2000000000000000000000bb", "aa002000000000000000000000bb"), + version_cmp( + b"aa2000000000000000000000bb", + b"aa002000000000000000000000bb" + ), Ordering::Equal, "Leading zeroes for numbers larger than u64::MAX are \ handled correctly without crashing" ); assert_eq!( - version_cmp(" a", "a"), + version_cmp(b" a", b"a"), Ordering::Greater, "Whitespace is after letters because letters are before non-letters" ); assert_eq!( - version_cmp("a~", "ab"), + version_cmp(b"a~", b"ab"), Ordering::Less, "A tilde is before other letters" ); assert_eq!( - version_cmp("a~", "a"), + version_cmp(b"a~", b"a"), Ordering::Less, "A tilde is before the line end" ); assert_eq!( - version_cmp("~", ""), + version_cmp(b"~", b""), Ordering::Greater, "A tilde is after the empty string" ); assert_eq!( - version_cmp(".f", ".1"), + version_cmp(b".f", b".1"), Ordering::Greater, "if both start with a dot it is ignored for the comparison" ); @@ -342,17 +354,17 @@ mod tests { // The following tests are incompatible with GNU as of 2021/06. // I think that's because of a bug in GNU, reported as https://lists.gnu.org/archive/html/bug-coreutils/2021-06/msg00045.html assert_eq!( - version_cmp("a..a", "a.+"), + version_cmp(b"a..a", b"a.+"), Ordering::Less, ".a is stripped before the comparison" ); assert_eq!( - version_cmp("a.", "a+"), + version_cmp(b"a.", b"a+"), Ordering::Greater, ". is not stripped before the comparison" ); assert_eq!( - version_cmp("a\0a", "a"), + version_cmp(b"a\0a", b"a"), Ordering::Greater, "NULL bytes are handled comparison" ); diff --git a/tests/fixtures/sort/keys_closed_range.expected.debug b/tests/fixtures/sort/keys_closed_range.expected.debug index e317d4079..a26442016 100644 --- a/tests/fixtures/sort/keys_closed_range.expected.debug +++ b/tests/fixtures/sort/keys_closed_range.expected.debug @@ -8,11 +8,11 @@ aa bb cc _ ________ èè éé èè - _ -________ -👩‍🔬 👩‍🔬 👩‍🔬 - __ -________ -💣💣 💣💣 💣💣 - __ + _ ______________ +👩‍🔬 👩‍🔬 👩‍🔬 + _ +___________________________________ +💣💣 💣💣 💣💣 + _ +__________________________ diff --git a/tests/fixtures/sort/keys_multiple_ranges.expected.debug b/tests/fixtures/sort/keys_multiple_ranges.expected.debug index 41b7e210d..7d74b3e87 100644 --- a/tests/fixtures/sort/keys_multiple_ranges.expected.debug +++ b/tests/fixtures/sort/keys_multiple_ranges.expected.debug @@ -11,14 +11,14 @@ aa bb cc ___ ________ èè éé èè - ___ - ___ -________ -👩‍🔬 👩‍🔬 👩‍🔬 - ___ - ___ -________ -💣💣 💣💣 💣💣 _____ _____ ______________ +👩‍🔬 👩‍🔬 👩‍🔬 + ____________ + ____________ +___________________________________ +💣💣 💣💣 💣💣 + _________ + _________ +__________________________ diff --git a/tests/fixtures/sort/keys_no_field_match.expected.debug b/tests/fixtures/sort/keys_no_field_match.expected.debug index 0a3ea8303..454d0eb1a 100644 --- a/tests/fixtures/sort/keys_no_field_match.expected.debug +++ b/tests/fixtures/sort/keys_no_field_match.expected.debug @@ -8,11 +8,11 @@ gg aa cc ^ no match for key ________ èè éé èè - ^ no match for key -________ -👩‍🔬 👩‍🔬 👩‍🔬 - ^ no match for key -________ -💣💣 💣💣 💣💣 ^ no match for key ______________ +👩‍🔬 👩‍🔬 👩‍🔬 + ^ no match for key +___________________________________ +💣💣 💣💣 💣💣 + ^ no match for key +__________________________ diff --git a/tests/fixtures/sort/keys_open_ended.expected b/tests/fixtures/sort/keys_open_ended.expected index 09e4e8729..3ed7d6274 100644 --- a/tests/fixtures/sort/keys_open_ended.expected +++ b/tests/fixtures/sort/keys_open_ended.expected @@ -1,6 +1,6 @@ gg aa cc dd aa ff aa bb cc -èè éé èè 👩‍🔬 👩‍🔬 👩‍🔬 💣💣 💣💣 💣💣 +èè éé èè diff --git a/tests/fixtures/sort/keys_open_ended.expected.debug b/tests/fixtures/sort/keys_open_ended.expected.debug index c8e4ad9ae..58a5062cd 100644 --- a/tests/fixtures/sort/keys_open_ended.expected.debug +++ b/tests/fixtures/sort/keys_open_ended.expected.debug @@ -7,12 +7,12 @@ ________ aa bb cc ____ ________ -èè éé èè - ____ -________ 👩‍🔬 👩‍🔬 👩‍🔬 - _____ -________ + ______________________ +___________________________________ 💣💣 💣💣 💣💣 - _______ + ________________ +__________________________ +èè éé èè + ________ ______________ diff --git a/tests/test_util_name.rs b/tests/test_util_name.rs index efa8fe08a..a9fbb29d9 100644 --- a/tests/test_util_name.rs +++ b/tests/test_util_name.rs @@ -46,6 +46,7 @@ fn execution_phrase_double() { } #[test] +#[ignore = "Test assumes error upon non-UTF8"] #[cfg(feature = "sort")] fn util_name_double() { use std::{ @@ -71,6 +72,7 @@ fn util_name_double() { } #[test] +#[ignore = "Test assumes error upon non-UTF8"] #[cfg(feature = "sort")] #[cfg(unix)] fn util_name_single() {