1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-28 11:37:44 +00:00

Merge pull request #3976 from andrewbaptist/implement_suffix_start

Add support for starting suffix numbers
This commit is contained in:
Sylvestre Ledru 2022-10-07 21:07:07 +02:00 committed by GitHub
commit 97dd4824e5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 244 additions and 160 deletions

View file

@ -28,6 +28,7 @@
use crate::number::DynamicWidthNumber; use crate::number::DynamicWidthNumber;
use crate::number::FixedWidthNumber; use crate::number::FixedWidthNumber;
use crate::number::Number; use crate::number::Number;
use uucore::error::{UResult, USimpleError};
/// The format to use for suffixes in the filename for each output chunk. /// The format to use for suffixes in the filename for each output chunk.
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
@ -119,19 +120,28 @@ impl<'a> FilenameIterator<'a> {
additional_suffix: &'a str, additional_suffix: &'a str,
suffix_length: usize, suffix_length: usize,
suffix_type: SuffixType, suffix_type: SuffixType,
) -> FilenameIterator<'a> { suffix_start: usize,
) -> UResult<FilenameIterator<'a>> {
let radix = suffix_type.radix(); let radix = suffix_type.radix();
let number = if suffix_length == 0 { let number = if suffix_length == 0 {
Number::DynamicWidth(DynamicWidthNumber::new(radix)) Number::DynamicWidth(DynamicWidthNumber::new(radix, suffix_start))
} else { } else {
Number::FixedWidth(FixedWidthNumber::new(radix, suffix_length)) Number::FixedWidth(
FixedWidthNumber::new(radix, suffix_length, suffix_start).map_err(|_| {
USimpleError::new(
1,
"numerical suffix start value is too large for the suffix length",
)
})?,
)
}; };
FilenameIterator {
Ok(FilenameIterator {
prefix, prefix,
additional_suffix, additional_suffix,
number, number,
first_iteration: true, first_iteration: true,
} })
} }
} }
@ -161,36 +171,36 @@ mod tests {
#[test] #[test]
fn test_filename_iterator_alphabetic_fixed_width() { fn test_filename_iterator_alphabetic_fixed_width() {
let mut it = FilenameIterator::new("chunk_", ".txt", 2, SuffixType::Alphabetic); let mut it = FilenameIterator::new("chunk_", ".txt", 2, SuffixType::Alphabetic, 0).unwrap();
assert_eq!(it.next().unwrap(), "chunk_aa.txt"); assert_eq!(it.next().unwrap(), "chunk_aa.txt");
assert_eq!(it.next().unwrap(), "chunk_ab.txt"); assert_eq!(it.next().unwrap(), "chunk_ab.txt");
assert_eq!(it.next().unwrap(), "chunk_ac.txt"); assert_eq!(it.next().unwrap(), "chunk_ac.txt");
let mut it = FilenameIterator::new("chunk_", ".txt", 2, SuffixType::Alphabetic); let mut it = FilenameIterator::new("chunk_", ".txt", 2, SuffixType::Alphabetic, 0).unwrap();
assert_eq!(it.nth(26 * 26 - 1).unwrap(), "chunk_zz.txt"); assert_eq!(it.nth(26 * 26 - 1).unwrap(), "chunk_zz.txt");
assert_eq!(it.next(), None); assert_eq!(it.next(), None);
} }
#[test] #[test]
fn test_filename_iterator_numeric_fixed_width() { fn test_filename_iterator_numeric_fixed_width() {
let mut it = FilenameIterator::new("chunk_", ".txt", 2, SuffixType::Decimal); let mut it = FilenameIterator::new("chunk_", ".txt", 2, SuffixType::Decimal, 0).unwrap();
assert_eq!(it.next().unwrap(), "chunk_00.txt"); assert_eq!(it.next().unwrap(), "chunk_00.txt");
assert_eq!(it.next().unwrap(), "chunk_01.txt"); assert_eq!(it.next().unwrap(), "chunk_01.txt");
assert_eq!(it.next().unwrap(), "chunk_02.txt"); assert_eq!(it.next().unwrap(), "chunk_02.txt");
let mut it = FilenameIterator::new("chunk_", ".txt", 2, SuffixType::Decimal); let mut it = FilenameIterator::new("chunk_", ".txt", 2, SuffixType::Decimal, 0).unwrap();
assert_eq!(it.nth(10 * 10 - 1).unwrap(), "chunk_99.txt"); assert_eq!(it.nth(10 * 10 - 1).unwrap(), "chunk_99.txt");
assert_eq!(it.next(), None); assert_eq!(it.next(), None);
} }
#[test] #[test]
fn test_filename_iterator_alphabetic_dynamic_width() { fn test_filename_iterator_alphabetic_dynamic_width() {
let mut it = FilenameIterator::new("chunk_", ".txt", 0, SuffixType::Alphabetic); let mut it = FilenameIterator::new("chunk_", ".txt", 0, SuffixType::Alphabetic, 0).unwrap();
assert_eq!(it.next().unwrap(), "chunk_aa.txt"); assert_eq!(it.next().unwrap(), "chunk_aa.txt");
assert_eq!(it.next().unwrap(), "chunk_ab.txt"); assert_eq!(it.next().unwrap(), "chunk_ab.txt");
assert_eq!(it.next().unwrap(), "chunk_ac.txt"); assert_eq!(it.next().unwrap(), "chunk_ac.txt");
let mut it = FilenameIterator::new("chunk_", ".txt", 0, SuffixType::Alphabetic); let mut it = FilenameIterator::new("chunk_", ".txt", 0, SuffixType::Alphabetic, 0).unwrap();
assert_eq!(it.nth(26 * 25 - 1).unwrap(), "chunk_yz.txt"); 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_zaaa.txt");
assert_eq!(it.next().unwrap(), "chunk_zaab.txt"); assert_eq!(it.next().unwrap(), "chunk_zaab.txt");
@ -198,14 +208,49 @@ mod tests {
#[test] #[test]
fn test_filename_iterator_numeric_dynamic_width() { fn test_filename_iterator_numeric_dynamic_width() {
let mut it = FilenameIterator::new("chunk_", ".txt", 0, SuffixType::Decimal); let mut it = FilenameIterator::new("chunk_", ".txt", 0, SuffixType::Decimal, 0).unwrap();
assert_eq!(it.next().unwrap(), "chunk_00.txt"); assert_eq!(it.next().unwrap(), "chunk_00.txt");
assert_eq!(it.next().unwrap(), "chunk_01.txt"); assert_eq!(it.next().unwrap(), "chunk_01.txt");
assert_eq!(it.next().unwrap(), "chunk_02.txt"); assert_eq!(it.next().unwrap(), "chunk_02.txt");
let mut it = FilenameIterator::new("chunk_", ".txt", 0, SuffixType::Decimal); let mut it = FilenameIterator::new("chunk_", ".txt", 0, SuffixType::Decimal, 0).unwrap();
assert_eq!(it.nth(10 * 9 - 1).unwrap(), "chunk_89.txt"); 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_9000.txt");
assert_eq!(it.next().unwrap(), "chunk_9001.txt"); assert_eq!(it.next().unwrap(), "chunk_9001.txt");
} }
#[test]
fn test_filename_iterator_numeric_suffix_decimal() {
let mut it = FilenameIterator::new("chunk_", ".txt", 0, SuffixType::Decimal, 5).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", 0, SuffixType::Hexadecimal, 9).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).unwrap();
assert_eq!(it.next().unwrap(), "chunk_999.txt");
assert!(it.next().is_none());
let it = FilenameIterator::new("chunk_", ".txt", 3, SuffixType::Decimal, 1000);
assert!(it.is_err());
let mut it =
FilenameIterator::new("chunk_", ".txt", 3, SuffixType::Hexadecimal, 0xfff).unwrap();
assert_eq!(it.next().unwrap(), "chunk_fff.txt");
assert!(it.next().is_none());
let it = FilenameIterator::new("chunk_", ".txt", 3, SuffixType::Hexadecimal, 0x1000);
assert!(it.is_err());
}
} }

View file

@ -100,10 +100,10 @@ impl Number {
/// differently and we only intend to use these numbers for display /// differently and we only intend to use these numbers for display
/// purposes and not for mathematical purposes. /// purposes and not for mathematical purposes.
#[allow(dead_code)] #[allow(dead_code)]
fn digits(&self) -> &Vec<u8> { fn digits(&self) -> Vec<u8> {
match self { match self {
Self::FixedWidth(number) => &number.digits, Self::FixedWidth(number) => number.digits.clone(),
Self::DynamicWidth(number) => &number.digits, Self::DynamicWidth(number) => number.digits(),
} }
} }
@ -175,7 +175,7 @@ impl Display for Number {
/// ///
/// # Displaying /// # Displaying
/// ///
/// This number is only displayable if `radix` is 10, 26, or 26. If /// This number is only displayable if `radix` is 10, 16, or 26. If
/// `radix` is 10 or 16, then the digits are concatenated and /// `radix` is 10 or 16, then the digits are concatenated and
/// displayed as a fixed-width decimal or hexadecimal number, /// displayed as a fixed-width decimal or hexadecimal number,
/// respectively. If `radix` is 26, then each digit is translated to /// respectively. If `radix` is 26, then each digit is translated to
@ -189,10 +189,21 @@ pub struct FixedWidthNumber {
impl FixedWidthNumber { impl FixedWidthNumber {
/// Instantiate a number of the given radix and width. /// Instantiate a number of the given radix and width.
pub fn new(radix: u8, width: usize) -> Self { pub fn new(radix: u8, width: usize, mut suffix_start: usize) -> Result<Self, Overflow> {
Self { let mut digits = vec![0_u8; width];
radix,
digits: vec![0; width], for i in (0..digits.len()).rev() {
let remainder = (suffix_start % (radix as usize)) as u8;
suffix_start /= radix as usize;
digits[i] = remainder;
if suffix_start == 0 {
break;
}
}
if suffix_start != 0 {
Err(Overflow)
} else {
Ok(Self { radix, digits })
} }
} }
@ -229,26 +240,13 @@ impl FixedWidthNumber {
impl Display for FixedWidthNumber { impl Display for FixedWidthNumber {
fn fmt(&self, f: &mut Formatter) -> fmt::Result { fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self.radix {
10 => {
let digits: String = self.digits.iter().map(|d| (b'0' + d) as char).collect();
write!(f, "{}", digits)
}
16 => {
let digits: String = self let digits: String = self
.digits .digits
.iter() .iter()
.map(|d| (if *d < 10 { b'0' + d } else { b'a' + (d - 10) }) as char) .map(|d| map_digit(self.radix, *d))
.collect(); .collect();
write!(f, "{}", digits) write!(f, "{}", digits)
} }
26 => {
let digits: String = self.digits.iter().map(|d| (b'a' + d) as char).collect();
write!(f, "{}", digits)
}
_ => Err(fmt::Error),
}
}
} }
/// A positional notation representation of a number of dynamically growing width. /// A positional notation representation of a number of dynamically growing width.
@ -293,105 +291,74 @@ impl Display for FixedWidthNumber {
#[derive(Clone)] #[derive(Clone)]
pub struct DynamicWidthNumber { pub struct DynamicWidthNumber {
radix: u8, radix: u8,
digits: Vec<u8>, current: usize,
} }
impl DynamicWidthNumber { impl DynamicWidthNumber {
/// Instantiate a number of the given radix, starting with width 2. pub fn new(radix: u8, suffix_start: usize) -> Self {
///
/// This associated function returns a new instance of the struct
/// with the given radix and a width of two digits, both 0.
pub fn new(radix: u8) -> Self {
Self { Self {
radix, radix,
digits: vec![0, 0], current: suffix_start,
} }
} }
/// Set all digits to zero.
fn reset(&mut self) {
for i in 0..self.digits.len() {
self.digits[i] = 0;
}
}
/// Increment this number.
///
/// This method adds one to this number. The first time that the
/// most significant digit would achieve its highest possible
/// value (that is, `radix - 1`), then all the digits get reset to
/// 0 and the number of digits increases by one.
///
/// This method never returns an error.
fn increment(&mut self) -> Result<(), Overflow> { fn increment(&mut self) -> Result<(), Overflow> {
for i in (0..self.digits.len()).rev() { self.current += 1;
// Increment the current digit.
self.digits[i] += 1;
// If the digit overflows, then set it to 0 and continue
// to the next iteration to increment the next most
// significant digit. Otherwise, terminate the loop, since
// there will be no further changes to any higher order
// digits.
if self.digits[i] == self.radix {
self.digits[i] = 0;
} else {
break;
}
}
// If the most significant digit is at its maximum value, then
// add another digit and reset all digits zero.
if self.digits[0] == self.radix - 1 {
self.digits.push(0);
self.reset();
}
Ok(()) Ok(())
} }
fn digits(&self) -> Vec<u8> {
let radix = self.radix as usize;
let mut remaining = self.current;
let mut sub_value = (radix - 1) * radix;
let mut num_fill_chars = 2;
// Convert the number into "num_fill_chars" and "remaining"
while remaining >= sub_value {
remaining -= sub_value;
sub_value *= radix;
num_fill_chars += 1;
}
// Convert the "remainder" to digits
let mut digits = Vec::new();
while remaining > 0 {
digits.push((remaining % radix) as u8);
remaining /= radix;
}
// Left pad the vec
digits.resize(num_fill_chars, 0);
digits.reverse();
digits
}
}
fn map_digit(radix: u8, d: u8) -> char {
(match radix {
10 => b'0' + d,
16 => {
if d < 10 {
b'0' + d
} else {
b'a' + (d - 10)
}
}
26 => b'a' + d,
_ => 0,
}) as char
} }
impl Display for DynamicWidthNumber { impl Display for DynamicWidthNumber {
fn fmt(&self, f: &mut Formatter) -> fmt::Result { fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self.radix {
10 => {
let num_fill_chars = self.digits.len() - 2;
let digits: String = self.digits.iter().map(|d| (b'0' + d) as char).collect();
write!(
f,
"{empty:9<num_fill_chars$}{digits}",
empty = "",
num_fill_chars = num_fill_chars,
digits = digits,
)
}
16 => {
let num_fill_chars = self.digits.len() - 2;
let digits: String = self let digits: String = self
.digits .digits()
.iter() .iter()
.map(|d| (if *d < 10 { b'0' + d } else { b'a' + (d - 10) }) as char) .map(|d| map_digit(self.radix, *d))
.collect(); .collect();
write!( let fill: String = (0..digits.len() - 2)
f, .map(|_| map_digit(self.radix, self.radix - 1))
"{empty:f<num_fill_chars$}{digits}", .collect();
empty = "", write!(f, "{fill}{digits}")
num_fill_chars = num_fill_chars,
digits = digits,
)
}
26 => {
let num_fill_chars = self.digits.len() - 2;
let digits: String = self.digits.iter().map(|d| (b'a' + d) as char).collect();
write!(
f,
"{empty:z<num_fill_chars$}{digits}",
empty = "",
num_fill_chars = num_fill_chars,
digits = digits,
)
}
_ => Err(fmt::Error),
}
} }
} }
@ -404,35 +371,36 @@ mod tests {
#[test] #[test]
fn test_dynamic_width_number_increment() { fn test_dynamic_width_number_increment() {
let mut n = Number::DynamicWidth(DynamicWidthNumber::new(3)); println!("Here");
assert_eq!(n.digits(), &vec![0, 0]); let mut n = Number::DynamicWidth(DynamicWidthNumber::new(3, 0));
assert_eq!(n.digits(), vec![0, 0]);
n.increment().unwrap(); n.increment().unwrap();
assert_eq!(n.digits(), &vec![0, 1]); assert_eq!(n.digits(), vec![0, 1]);
n.increment().unwrap(); n.increment().unwrap();
assert_eq!(n.digits(), &vec![0, 2]); assert_eq!(n.digits(), vec![0, 2]);
n.increment().unwrap(); n.increment().unwrap();
assert_eq!(n.digits(), &vec![1, 0]); assert_eq!(n.digits(), vec![1, 0]);
n.increment().unwrap(); n.increment().unwrap();
assert_eq!(n.digits(), &vec![1, 1]); assert_eq!(n.digits(), vec![1, 1]);
n.increment().unwrap(); n.increment().unwrap();
assert_eq!(n.digits(), &vec![1, 2]); assert_eq!(n.digits(), vec![1, 2]);
n.increment().unwrap(); n.increment().unwrap();
assert_eq!(n.digits(), &vec![0, 0, 0]); assert_eq!(n.digits(), vec![0, 0, 0]);
n.increment().unwrap(); n.increment().unwrap();
assert_eq!(n.digits(), &vec![0, 0, 1]); assert_eq!(n.digits(), vec![0, 0, 1]);
} }
#[test] #[test]
fn test_dynamic_width_number_display_alphabetic() { fn test_dynamic_width_number_display_alphabetic() {
fn num(n: usize) -> Number { fn num(n: usize) -> Number {
let mut number = Number::DynamicWidth(DynamicWidthNumber::new(26)); let mut number = Number::DynamicWidth(DynamicWidthNumber::new(26, 0));
for _ in 0..n { for _ in 0..n {
number.increment().unwrap(); number.increment().unwrap();
} }
@ -456,7 +424,7 @@ mod tests {
#[test] #[test]
fn test_dynamic_width_number_display_numeric_decimal() { fn test_dynamic_width_number_display_numeric_decimal() {
fn num(n: usize) -> Number { fn num(n: usize) -> Number {
let mut number = Number::DynamicWidth(DynamicWidthNumber::new(10)); let mut number = Number::DynamicWidth(DynamicWidthNumber::new(10, 0));
for _ in 0..n { for _ in 0..n {
number.increment().unwrap(); number.increment().unwrap();
} }
@ -477,7 +445,7 @@ mod tests {
#[test] #[test]
fn test_dynamic_width_number_display_numeric_hexadecimal() { fn test_dynamic_width_number_display_numeric_hexadecimal() {
fn num(n: usize) -> Number { fn num(n: usize) -> Number {
let mut number = Number::DynamicWidth(DynamicWidthNumber::new(16)); let mut number = Number::DynamicWidth(DynamicWidthNumber::new(16, 0));
for _ in 0..n { for _ in 0..n {
number.increment().unwrap(); number.increment().unwrap();
} }
@ -500,31 +468,31 @@ mod tests {
#[test] #[test]
fn test_fixed_width_number_increment() { fn test_fixed_width_number_increment() {
let mut n = Number::FixedWidth(FixedWidthNumber::new(3, 2)); let mut n = Number::FixedWidth(FixedWidthNumber::new(3, 2, 0).unwrap());
assert_eq!(n.digits(), &vec![0, 0]); assert_eq!(n.digits(), vec![0, 0]);
n.increment().unwrap(); n.increment().unwrap();
assert_eq!(n.digits(), &vec![0, 1]); assert_eq!(n.digits(), vec![0, 1]);
n.increment().unwrap(); n.increment().unwrap();
assert_eq!(n.digits(), &vec![0, 2]); assert_eq!(n.digits(), vec![0, 2]);
n.increment().unwrap(); n.increment().unwrap();
assert_eq!(n.digits(), &vec![1, 0]); assert_eq!(n.digits(), vec![1, 0]);
n.increment().unwrap(); n.increment().unwrap();
assert_eq!(n.digits(), &vec![1, 1]); assert_eq!(n.digits(), vec![1, 1]);
n.increment().unwrap(); n.increment().unwrap();
assert_eq!(n.digits(), &vec![1, 2]); assert_eq!(n.digits(), vec![1, 2]);
n.increment().unwrap(); n.increment().unwrap();
assert_eq!(n.digits(), &vec![2, 0]); assert_eq!(n.digits(), vec![2, 0]);
n.increment().unwrap(); n.increment().unwrap();
assert_eq!(n.digits(), &vec![2, 1]); assert_eq!(n.digits(), vec![2, 1]);
n.increment().unwrap(); n.increment().unwrap();
assert_eq!(n.digits(), &vec![2, 2]); assert_eq!(n.digits(), vec![2, 2]);
assert!(n.increment().is_err()); assert!(n.increment().is_err());
} }
#[test] #[test]
fn test_fixed_width_number_display_alphabetic() { fn test_fixed_width_number_display_alphabetic() {
fn num(n: usize) -> Result<Number, Overflow> { fn num(n: usize) -> Result<Number, Overflow> {
let mut number = Number::FixedWidth(FixedWidthNumber::new(26, 2)); let mut number = Number::FixedWidth(FixedWidthNumber::new(26, 2, 0).unwrap());
for _ in 0..n { for _ in 0..n {
number.increment()?; number.increment()?;
} }
@ -549,7 +517,7 @@ mod tests {
#[test] #[test]
fn test_fixed_width_number_display_numeric_decimal() { fn test_fixed_width_number_display_numeric_decimal() {
fn num(n: usize) -> Result<Number, Overflow> { fn num(n: usize) -> Result<Number, Overflow> {
let mut number = Number::FixedWidth(FixedWidthNumber::new(10, 2)); let mut number = Number::FixedWidth(FixedWidthNumber::new(10, 2, 0).unwrap());
for _ in 0..n { for _ in 0..n {
number.increment()?; number.increment()?;
} }
@ -568,7 +536,7 @@ mod tests {
#[test] #[test]
fn test_fixed_width_number_display_numeric_hexadecimal() { fn test_fixed_width_number_display_numeric_hexadecimal() {
fn num(n: usize) -> Result<Number, Overflow> { fn num(n: usize) -> Result<Number, Overflow> {
let mut number = Number::FixedWidth(FixedWidthNumber::new(16, 2)); let mut number = Number::FixedWidth(FixedWidthNumber::new(16, 2, 0).unwrap());
for _ in 0..n { for _ in 0..n {
number.increment()?; number.increment()?;
} }
@ -583,4 +551,32 @@ mod tests {
assert_eq!(format!("{}", num(16 * 16 - 1).unwrap()), "ff"); assert_eq!(format!("{}", num(16 * 16 - 1).unwrap()), "ff");
assert!(num(16 * 16).is_err()); assert!(num(16 * 16).is_err());
} }
#[test]
fn test_fixed_width_number_start_suffix() {
fn num(n: usize) -> Result<Number, Overflow> {
let mut number = Number::FixedWidth(FixedWidthNumber::new(16, 2, 0x14)?);
for _ in 0..n {
number.increment()?;
}
Ok(number)
}
assert_eq!(format!("{}", num(0).unwrap()), "14");
assert_eq!(format!("{}", num(0xf).unwrap()), "23");
}
#[test]
fn test_dynamic_width_number_start_suffix() {
fn num(n: usize) -> Result<Number, Overflow> {
let mut number = Number::DynamicWidth(DynamicWidthNumber::new(10, 8));
for _ in 0..n {
number.increment()?;
}
Ok(number)
}
assert_eq!(format!("{}", num(0).unwrap()), "08");
assert_eq!(format!("{}", num(8).unwrap()), "16");
}
} }

View file

@ -144,7 +144,7 @@ pub fn uu_app<'a>() -> Command<'a> {
.takes_value(true) .takes_value(true)
.value_name("N") .value_name("N")
.default_value(OPT_DEFAULT_SUFFIX_LENGTH) .default_value(OPT_DEFAULT_SUFFIX_LENGTH)
.help("use suffixes of length N (default 2)"), .help("use suffixes of fixed length N. 0 implies dynamic length."),
) )
.arg( .arg(
Arg::new(OPT_HEX_SUFFIXES) Arg::new(OPT_HEX_SUFFIXES)
@ -400,13 +400,23 @@ impl Strategy {
} }
/// Parse the suffix type from the command-line arguments. /// Parse the suffix type from the command-line arguments.
fn suffix_type_from(matches: &ArgMatches) -> SuffixType { fn suffix_type_from(matches: &ArgMatches) -> Result<(SuffixType, usize), SettingsError> {
if matches.value_source(OPT_NUMERIC_SUFFIXES) == Some(ValueSource::CommandLine) { if matches.value_source(OPT_NUMERIC_SUFFIXES) == Some(ValueSource::CommandLine) {
SuffixType::Decimal let suffix_start = matches.value_of(OPT_NUMERIC_SUFFIXES);
let suffix_start = suffix_start.ok_or(SettingsError::SuffixNotParsable(String::new()))?;
let suffix_start = suffix_start
.parse()
.map_err(|_| SettingsError::SuffixNotParsable(suffix_start.to_string()))?;
Ok((SuffixType::Decimal, suffix_start))
} else if matches.value_source(OPT_HEX_SUFFIXES) == Some(ValueSource::CommandLine) { } else if matches.value_source(OPT_HEX_SUFFIXES) == Some(ValueSource::CommandLine) {
SuffixType::Hexadecimal let suffix_start = matches.value_of(OPT_HEX_SUFFIXES);
let suffix_start = suffix_start.ok_or(SettingsError::SuffixNotParsable(String::new()))?;
let suffix_start = usize::from_str_radix(suffix_start, 16)
.map_err(|_| SettingsError::SuffixNotParsable(suffix_start.to_string()))?;
Ok((SuffixType::Hexadecimal, suffix_start))
} else { } else {
SuffixType::Alphabetic // no numeric/hex suffix
Ok((SuffixType::Alphabetic, 0))
} }
} }
@ -418,6 +428,7 @@ struct Settings {
prefix: String, prefix: String,
suffix_type: SuffixType, suffix_type: SuffixType,
suffix_length: usize, suffix_length: usize,
suffix_start: usize,
additional_suffix: String, additional_suffix: String,
input: String, input: String,
/// When supplied, a shell command to output to instead of xaa, xab … /// When supplied, a shell command to output to instead of xaa, xab …
@ -497,7 +508,7 @@ impl Settings {
return Err(SettingsError::SuffixContainsSeparator(additional_suffix)); return Err(SettingsError::SuffixContainsSeparator(additional_suffix));
} }
let strategy = Strategy::from(matches).map_err(SettingsError::Strategy)?; let strategy = Strategy::from(matches).map_err(SettingsError::Strategy)?;
let suffix_type = suffix_type_from(matches); let (suffix_type, suffix_start) = suffix_type_from(matches)?;
let suffix_length_str = matches.get_one::<String>(OPT_SUFFIX_LENGTH).unwrap(); let suffix_length_str = matches.get_one::<String>(OPT_SUFFIX_LENGTH).unwrap();
let suffix_length: usize = suffix_length_str let suffix_length: usize = suffix_length_str
.parse() .parse()
@ -517,6 +528,7 @@ impl Settings {
.parse() .parse()
.map_err(|_| SettingsError::SuffixNotParsable(suffix_length_str.to_string()))?, .map_err(|_| SettingsError::SuffixNotParsable(suffix_length_str.to_string()))?,
suffix_type, suffix_type,
suffix_start,
additional_suffix, additional_suffix,
verbose: matches.value_source("verbose") == Some(ValueSource::CommandLine), verbose: matches.value_source("verbose") == Some(ValueSource::CommandLine),
strategy, strategy,
@ -589,7 +601,8 @@ impl<'a> ByteChunkWriter<'a> {
&settings.additional_suffix, &settings.additional_suffix,
settings.suffix_length, settings.suffix_length,
settings.suffix_type, settings.suffix_type,
); settings.suffix_start,
)?;
let filename = filename_iterator let filename = filename_iterator
.next() .next()
.ok_or_else(|| USimpleError::new(1, "output file suffixes exhausted"))?; .ok_or_else(|| USimpleError::new(1, "output file suffixes exhausted"))?;
@ -717,7 +730,8 @@ impl<'a> LineChunkWriter<'a> {
&settings.additional_suffix, &settings.additional_suffix,
settings.suffix_length, settings.suffix_length,
settings.suffix_type, settings.suffix_type,
); settings.suffix_start,
)?;
let filename = filename_iterator let filename = filename_iterator
.next() .next()
.ok_or_else(|| USimpleError::new(1, "output file suffixes exhausted"))?; .ok_or_else(|| USimpleError::new(1, "output file suffixes exhausted"))?;
@ -825,7 +839,8 @@ impl<'a> LineBytesChunkWriter<'a> {
&settings.additional_suffix, &settings.additional_suffix,
settings.suffix_length, settings.suffix_length,
settings.suffix_type, settings.suffix_type,
); settings.suffix_start,
)?;
let filename = filename_iterator let filename = filename_iterator
.next() .next()
.ok_or_else(|| USimpleError::new(1, "output file suffixes exhausted"))?; .ok_or_else(|| USimpleError::new(1, "output file suffixes exhausted"))?;
@ -1022,7 +1037,8 @@ where
&settings.additional_suffix, &settings.additional_suffix,
settings.suffix_length, settings.suffix_length,
settings.suffix_type, settings.suffix_type,
); settings.suffix_start,
)?;
// Create one writer for each chunk. This will create each // Create one writer for each chunk. This will create each
// of the underlying files (if not in `--filter` mode). // of the underlying files (if not in `--filter` mode).
@ -1098,7 +1114,8 @@ where
&settings.additional_suffix, &settings.additional_suffix,
settings.suffix_length, settings.suffix_length,
settings.suffix_type, settings.suffix_type,
); settings.suffix_start,
)?;
// Create one writer for each chunk. This will create each // Create one writer for each chunk. This will create each
// of the underlying files (if not in `--filter` mode). // of the underlying files (if not in `--filter` mode).

View file

@ -701,3 +701,29 @@ fn test_multiple_of_input_chunk() {
} }
assert_eq!(glob.collate(), at.read_bytes(name)); assert_eq!(glob.collate(), at.read_bytes(name));
} }
#[test]
fn test_numeric_suffix() {
let (at, mut ucmd) = at_and_ucmd!();
ucmd.args(&["-n", "4", "--numeric-suffixes", "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!();
ucmd.args(&["-n", "4", "--hex-suffixes", "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"), "");
}