diff --git a/src/uu/csplit/src/split_name.rs b/src/uu/csplit/src/split_name.rs index 4d94b56a923..49b24e70aaf 100644 --- a/src/uu/csplit/src/split_name.rs +++ b/src/uu/csplit/src/split_name.rs @@ -49,7 +49,7 @@ impl SplitName { None => Box::new(move |n: usize| -> String { format!("{prefix}{n:0n_digits$}") }), Some(custom) => { let spec = - Regex::new(r"(?P%((?P[0#-])(?P\d+)?)?(?P[diuoxX]))") + Regex::new(r"(?P%(?P[#0-]*)(?P[0-9]+)?(.(?P[0-9]?))?(?P[diuoxX]))") .unwrap(); let mut captures_iter = spec.captures_iter(&custom); let custom_fn: Box String> = match captures_iter.next() { @@ -61,7 +61,8 @@ impl SplitName { None => 0, Some(m) => m.as_str().parse::().unwrap(), }; - match (captures.name("FLAG"), captures.name("TYPE")) { + + match (captures.name("FLAGS"), captures.name("CONVERSION")) { (None, Some(ref t)) => match t.as_str() { "d" | "i" | "u" => Box::new(move |n: usize| -> String { format!("{prefix}{before}{n}{after}") @@ -77,66 +78,100 @@ impl SplitName { }), _ => return Err(CsplitError::SuffixFormatIncorrect), }, - (Some(ref f), Some(ref t)) => { - match (f.as_str(), t.as_str()) { - /* - * zero padding - */ - // decimal - ("0", "d" | "i" | "u") => Box::new(move |n: usize| -> String { - format!("{prefix}{before}{n:0width$}{after}") - }), - // octal - ("0", "o") => Box::new(move |n: usize| -> String { - format!("{prefix}{before}{n:0width$o}{after}") - }), - // lower hexadecimal - ("0", "x") => Box::new(move |n: usize| -> String { - format!("{prefix}{before}{n:0width$x}{after}") - }), - // upper hexadecimal - ("0", "X") => Box::new(move |n: usize| -> String { - format!("{prefix}{before}{n:0width$X}{after}") - }), - - /* - * Alternate form - */ - // octal - ("#", "o") => Box::new(move |n: usize| -> String { - format!("{prefix}{before}{n:>#width$o}{after}") - }), - // lower hexadecimal - ("#", "x") => Box::new(move |n: usize| -> String { - format!("{prefix}{before}{n:>#width$x}{after}") - }), - // upper hexadecimal - ("#", "X") => Box::new(move |n: usize| -> String { - format!("{prefix}{before}{n:>#width$X}{after}") - }), - - /* - * Left adjusted - */ - // decimal - ("-", "d" | "i" | "u") => Box::new(move |n: usize| -> String { - format!("{prefix}{before}{n:<#width$}{after}") - }), - // octal - ("-", "o") => Box::new(move |n: usize| -> String { - format!("{prefix}{before}{n:<#width$o}{after}") - }), - // lower hexadecimal - ("-", "x") => Box::new(move |n: usize| -> String { - format!("{prefix}{before}{n:<#width$x}{after}") - }), - // upper hexadecimal - ("-", "X") => Box::new(move |n: usize| -> String { - format!("{prefix}{before}{n:<#width$X}{after}") - }), - - _ => return Err(CsplitError::SuffixFormatIncorrect), + (Some(matched_flags), Some(ref matched_conversion)) => { + let flags = matched_flags.as_str().to_owned(); + let conversion = matched_conversion.as_str().to_owned(); + + let mut flag_alternative = false; + let mut flag_zero = false; + let mut flag_minus = false; + for char in flags.chars() { + match char { + '#' => flag_alternative = true, + '0' => flag_zero = true, + '-' => flag_minus = true, + _ => unreachable!( + "Flags should be already filtered by the regex: received {char}" + ), + } + } + + // Interaction between flags: minus cancels zero + if flag_minus { + flag_zero = false; } + + // Alternative flag is not compatible with decimal conversions + if (conversion == "d" || conversion == "i" || conversion == "u") + && flag_alternative + { + return Err(CsplitError::SuffixFormatIncorrect); + } + + let precision = match captures.name("PRECISION") { + Some(m) => { + // precision cancels the flag_zero + flag_zero = false; + let precision_str = m.as_str(); + // only one dot could be given + // in this case, default precision becomes 0 + if precision_str.is_empty() { + 0 + } else { + precision_str.parse::().unwrap() + } + } + None => { + //default precision is 1 for d,i,u,o,x,X + 1 + } + }; + + Box::new(move |n: usize| -> String { + // First step: Formatting the number with precision, zeros, alternative style... + let precision_formatted = match conversion.as_str() { + "d" | "i" | "u" => match (n, precision) { + (0, 0) => String::new(), + (_, _) => format!("{n:0precision$}") + } + "o" => match (n, flag_alternative, precision) { + (0, true, _) => format!("{n:0>precision$o}"), + (0, false, 0) => String::new(), + (_, true, 0) => format!("0{n:o}"), + (_, true, _) => format!( + "{:0>precision$}", + format!("0{n:o}") + ), + (_, false, _) => format!("{n:0precision$o}"), + } + "x" => match (n, flag_alternative, precision) { + (0, _, 0) => String::new(), + (0, true, _) => format!("{n:0precision$x}"), + ( _,true, _) => format!("{n:#0size$x}", size = precision + 2 ), + ( _,false, 0) => format!("{n:precision$x}"), + (_, _, _) => format!("{n:0precision$x}") + } + "X" => match (n, flag_alternative, precision) { + (0, _, 0) => String::new(), + (0, true, _) => format!("{n:0precision$X}"), + ( _,true, _) => format!("{n:#0size$X}", size = precision + 2 ), + ( _,false, 0) => format!("{n:precision$X}"), + (_, _, _) => format!("{n:0precision$X}") + } + _ => unreachable!("Conversion are filtered by the regex : received {conversion}"), + } + ; + + // second step : Fit the number in the width with correct padding and filling + let width_formatted = match (flag_minus, flag_zero) { + (true, true) => format!("{precision_formatted:0 format!("{precision_formatted: format!("{precision_formatted:0>width$}"), + (false, false) => format!("{precision_formatted:>width$}"), + }; + + format!("{prefix}{before}{width_formatted}{after}") + }) } _ => return Err(CsplitError::SuffixFormatIncorrect), } @@ -194,6 +229,33 @@ mod tests { }; } + #[test] + fn invalid_suffix_format_plus() { + let split_name = SplitName::new(None, Some(String::from("%+")), None); + match split_name { + Err(CsplitError::SuffixFormatIncorrect) => (), + _ => panic!("should fail with SuffixFormatIncorrect"), + }; + } + + #[test] + fn invalid_suffix_format_space() { + let split_name = SplitName::new(None, Some(String::from("% ")), None); + match split_name { + Err(CsplitError::SuffixFormatIncorrect) => (), + _ => panic!("should fail with SuffixFormatIncorrect"), + }; + } + + #[test] + fn invalid_suffix_format_alternative_decimal() { + let split_name = SplitName::new(None, Some(String::from("%#d")), None); + match split_name { + Err(CsplitError::SuffixFormatIncorrect) => (), + _ => panic!("should fail with SuffixFormatIncorrect"), + }; + } + #[test] fn default_formatter() { let split_name = SplitName::new(None, None, None).unwrap(); @@ -261,25 +323,41 @@ mod tests { #[test] fn zero_padding_octal() { let split_name = SplitName::new(None, Some(String::from("cst-%03o-")), None).unwrap(); + assert_eq!(split_name.get(0), "xxcst-000-"); + assert_eq!(split_name.get(1), "xxcst-001-"); assert_eq!(split_name.get(42), "xxcst-052-"); } #[test] fn zero_padding_lower_hex() { let split_name = SplitName::new(None, Some(String::from("cst-%03x-")), None).unwrap(); + assert_eq!(split_name.get(0), "xxcst-000-"); + assert_eq!(split_name.get(1), "xxcst-001-"); assert_eq!(split_name.get(42), "xxcst-02a-"); } #[test] fn zero_padding_upper_hex() { let split_name = SplitName::new(None, Some(String::from("cst-%03X-")), None).unwrap(); + assert_eq!(split_name.get(0), "xxcst-000-"); + assert_eq!(split_name.get(1), "xxcst-001-"); assert_eq!(split_name.get(42), "xxcst-02A-"); } #[test] fn alternate_form_octal() { let split_name = SplitName::new(None, Some(String::from("cst-%#10o-")), None).unwrap(); - assert_eq!(split_name.get(42), "xxcst- 0o52-"); + assert_eq!(split_name.get(0), "xxcst- 0-"); + assert_eq!(split_name.get(1), "xxcst- 01-"); + assert_eq!(split_name.get(42), "xxcst- 052-"); + } + + #[test] + fn form_lower_hex_width() { + let split_name = SplitName::new(None, Some(String::from("cst-%06x-")), None).unwrap(); + assert_eq!(split_name.get(0), "xxcst-000000-"); + assert_eq!(split_name.get(1), "xxcst-000001-"); + assert_eq!(split_name.get(42), "xxcst-00002a-"); } #[test] @@ -294,6 +372,41 @@ mod tests { assert_eq!(split_name.get(42), "xxcst- 0x2A-"); } + #[test] + fn alternate_form_lower_hex_precision0() { + let split_name = SplitName::new(None, Some(String::from("cst-%#6.0x-")), None).unwrap(); + assert_eq!(split_name.get(0), "xxcst- -"); + assert_eq!(split_name.get(1), "xxcst- 0x1-"); + assert_eq!(split_name.get(42), "xxcst- 0x2a-"); + } + + #[test] + fn alternate_form_lower_hex_precision1() { + let split_name = SplitName::new(None, Some(String::from("cst-%#6.1x-")), None).unwrap(); + assert_eq!(split_name.get(0), "xxcst- 0-"); + assert_eq!(split_name.get(1), "xxcst- 0x1-"); + assert_eq!(split_name.get(2), "xxcst- 0x2-"); + assert_eq!(split_name.get(42), "xxcst- 0x2a-"); + } + + #[test] + fn alternate_form_lower_hex_precision2() { + let split_name = SplitName::new(None, Some(String::from("cst-%#6.2x-")), None).unwrap(); + assert_eq!(split_name.get(0), "xxcst- 00-"); + assert_eq!(split_name.get(1), "xxcst- 0x01-"); + assert_eq!(split_name.get(2), "xxcst- 0x02-"); + assert_eq!(split_name.get(42), "xxcst- 0x2a-"); + } + + #[test] + fn alternate_form_lower_hex_precision3() { + let split_name = SplitName::new(None, Some(String::from("cst-%#6.3x-")), None).unwrap(); + assert_eq!(split_name.get(0), "xxcst- 000-"); + assert_eq!(split_name.get(1), "xxcst- 0x001-"); + assert_eq!(split_name.get(2), "xxcst- 0x002-"); + assert_eq!(split_name.get(42), "xxcst- 0x02a-"); + } + #[test] fn left_adjusted_decimal1() { let split_name = SplitName::new(None, Some(String::from("cst-%-10d-")), None).unwrap(); @@ -312,22 +425,31 @@ mod tests { assert_eq!(split_name.get(42), "xxcst-42 -"); } + #[test] + fn left_adjusted_decimal_precision() { + let split_name = SplitName::new(None, Some(String::from("cst-%-10.3u-")), None).unwrap(); + assert_eq!(split_name.get(0), "xxcst-000 -"); + assert_eq!(split_name.get(1), "xxcst-001 -"); + assert_eq!(split_name.get(42), "xxcst-042 -"); + } + #[test] fn left_adjusted_octal() { let split_name = SplitName::new(None, Some(String::from("cst-%-10o-")), None).unwrap(); - assert_eq!(split_name.get(42), "xxcst-0o52 -"); + assert_eq!(split_name.get(42), "xxcst-52 -"); } #[test] fn left_adjusted_lower_hex() { let split_name = SplitName::new(None, Some(String::from("cst-%-10x-")), None).unwrap(); - assert_eq!(split_name.get(42), "xxcst-0x2a -"); + assert_eq!(split_name.get(42), "xxcst-2a -"); } #[test] fn left_adjusted_upper_hex() { let split_name = SplitName::new(None, Some(String::from("cst-%-10X-")), None).unwrap(); - assert_eq!(split_name.get(42), "xxcst-0x2A -"); + // assert_eq!(split_name.get(42), "xxcst-0x2A -"); + assert_eq!(split_name.get(42), "xxcst-2A -"); } #[test] @@ -338,4 +460,172 @@ mod tests { _ => panic!("should fail with SuffixFormatTooManyPercents"), }; } + + #[test] + fn precision_decimal0() { + let split_name = SplitName::new(None, Some(String::from("cst-%3.0u-")), None).unwrap(); + assert_eq!(split_name.get(0), "xxcst- -"); + assert_eq!(split_name.get(1), "xxcst- 1-"); + assert_eq!(split_name.get(2), "xxcst- 2-"); + } + + #[test] + fn precision_decimal1() { + let split_name = SplitName::new(None, Some(String::from("cst-%3.1u-")), None).unwrap(); + assert_eq!(split_name.get(0), "xxcst- 0-"); + assert_eq!(split_name.get(1), "xxcst- 1-"); + assert_eq!(split_name.get(2), "xxcst- 2-"); + } + + #[test] + fn alternate_octal() { + let split_name = SplitName::new(None, Some(String::from("%#6o")), None).unwrap(); + assert_eq!(split_name.get(0), "xx 0"); + assert_eq!(split_name.get(1), "xx 01"); + } + + #[test] + fn precision_octal0() { + let split_name = SplitName::new(None, Some(String::from("%.0o")), None).unwrap(); + assert_eq!(split_name.get(0), "xx"); + assert_eq!(split_name.get(1), "xx1"); + } + + #[test] + fn precision_octal1() { + let split_name = SplitName::new(None, Some(String::from("%.1o")), None).unwrap(); + assert_eq!(split_name.get(0), "xx0"); + assert_eq!(split_name.get(1), "xx1"); + } + + #[test] + fn precision_octal3() { + let split_name = SplitName::new(None, Some(String::from("%.3o")), None).unwrap(); + assert_eq!(split_name.get(0), "xx000"); + assert_eq!(split_name.get(1), "xx001"); + } + + #[test] + fn precision_lower_hex0() { + let split_name = SplitName::new(None, Some(String::from("%.0x")), None).unwrap(); + assert_eq!(split_name.get(0), "xx"); + assert_eq!(split_name.get(1), "xx1"); + } + + #[test] + fn precision_lower_hex1() { + let split_name = SplitName::new(None, Some(String::from("%.1x")), None).unwrap(); + assert_eq!(split_name.get(0), "xx0"); + assert_eq!(split_name.get(1), "xx1"); + } + + #[test] + fn precision_lower_hex3() { + let split_name = SplitName::new(None, Some(String::from("%.3x")), None).unwrap(); + assert_eq!(split_name.get(0), "xx000"); + assert_eq!(split_name.get(1), "xx001"); + } + + #[test] + fn precision_upper_hex0() { + let split_name = SplitName::new(None, Some(String::from("%.0x")), None).unwrap(); + assert_eq!(split_name.get(0), "xx"); + assert_eq!(split_name.get(1), "xx1"); + } + + #[test] + fn precision_upper_hex1() { + let split_name = SplitName::new(None, Some(String::from("%.1x")), None).unwrap(); + assert_eq!(split_name.get(0), "xx0"); + assert_eq!(split_name.get(1), "xx1"); + } + + #[test] + fn precision_upper_hex3() { + let split_name = SplitName::new(None, Some(String::from("%.3x")), None).unwrap(); + assert_eq!(split_name.get(0), "xx000"); + assert_eq!(split_name.get(1), "xx001"); + } + + #[test] + fn precision_alternate_lower_hex0() { + let split_name = SplitName::new(None, Some(String::from("%#10.0x")), None).unwrap(); + assert_eq!(split_name.get(0), "xx "); + assert_eq!(split_name.get(1), "xx 0x1"); + } + + #[test] + fn precision_alternate_lower_hex1() { + let split_name = SplitName::new(None, Some(String::from("%#10.1x")), None).unwrap(); + assert_eq!(split_name.get(0), "xx 0"); + assert_eq!(split_name.get(1), "xx 0x1"); + } + + #[test] + fn precision_alternate_lower_hex2() { + let split_name = SplitName::new(None, Some(String::from("%#10.2x")), None).unwrap(); + assert_eq!(split_name.get(0), "xx 00"); + assert_eq!(split_name.get(1), "xx 0x01"); + } + + #[test] + fn precision_alternate_octal0() { + let split_name = SplitName::new(None, Some(String::from("%#6.0o")), None).unwrap(); + assert_eq!(split_name.get(0), "xx 0"); + assert_eq!(split_name.get(1), "xx 01"); + } + + #[test] + fn precision_alternate_octal1() { + let split_name = SplitName::new(None, Some(String::from("%#6.1o")), None).unwrap(); + assert_eq!(split_name.get(0), "xx 0"); + assert_eq!(split_name.get(1), "xx 01"); + } + + #[test] + fn precision_alternate_octal2() { + let split_name = SplitName::new(None, Some(String::from("%#6.2o")), None).unwrap(); + assert_eq!(split_name.get(0), "xx 00"); + assert_eq!(split_name.get(1), "xx 01"); + } + + #[test] + fn precision_alternate_octal3() { + let split_name = SplitName::new(None, Some(String::from("%#6.3o")), None).unwrap(); + assert_eq!(split_name.get(0), "xx 000"); + assert_eq!(split_name.get(1), "xx 001"); + } + + #[test] + fn precision_only_dot_decimal() { + // if only one dot is given, precision becomes 0 + let split_name = SplitName::new(None, Some(String::from("%.u")), None).unwrap(); + assert_eq!(split_name.get(0), "xx"); + assert_eq!(split_name.get(1), "xx1"); + assert_eq!(split_name.get(42), "xx42"); + } + #[test] + fn precision_only_dot_octal() { + // if only one dot is given, precision becomes 0 + let split_name = SplitName::new(None, Some(String::from("%.o")), None).unwrap(); + assert_eq!(split_name.get(0), "xx"); + assert_eq!(split_name.get(1), "xx1"); + assert_eq!(split_name.get(42), "xx52"); + } + #[test] + fn precision_only_dot_lower_hex() { + // if only one dot is given, precision becomes 0 + let split_name = SplitName::new(None, Some(String::from("%.x")), None).unwrap(); + assert_eq!(split_name.get(0), "xx"); + assert_eq!(split_name.get(1), "xx1"); + assert_eq!(split_name.get(42), "xx2a"); + } + #[test] + fn precision_only_dot_upper_hex() { + // if only one dot is given, precision becomes 0 + let split_name = SplitName::new(None, Some(String::from("%.X")), None).unwrap(); + assert_eq!(split_name.get(0), "xx"); + assert_eq!(split_name.get(1), "xx1"); + assert_eq!(split_name.get(42), "xx2A"); + } } diff --git a/tests/by-util/test_csplit.rs b/tests/by-util/test_csplit.rs index b83d5e0eede..8f8053f22a4 100644 --- a/tests/by-util/test_csplit.rs +++ b/tests/by-util/test_csplit.rs @@ -1342,3 +1342,67 @@ fn test_line_num_range_with_up_to_match3() { assert_eq!(at.read("xx01"), ""); assert_eq!(at.read("xx02"), generate(10, 51)); } + +#[test] +fn test_suffix_with_precision_decimal() { + let (at, mut ucmd) = at_and_ucmd!(); + ucmd.args(&["numbers50.txt", "10", "--suffix", "%6.3d"]) + .succeeds() + .stdout_only("18\n123\n"); + + let count = glob(&at.plus_as_string("xx*")) + .expect("there should be splits created") + .count(); + + assert_eq!(count, 2); + assert_eq!(at.read("xx 000"), generate(1, 10)); + assert_eq!(at.read("xx 001"), generate(10, 51)); +} + +#[test] +fn test_suffix_with_alternative_precision_octal() { + let (at, mut ucmd) = at_and_ucmd!(); + ucmd.args(&["numbers50.txt", "10", "--suffix", "%#6.o"]) + .succeeds() + .stdout_only("18\n123\n"); + + let count = glob(&at.plus_as_string("xx*")) + .expect("there should be splits created") + .count(); + + assert_eq!(count, 2); + assert_eq!(at.read("xx 0"), generate(1, 10)); + assert_eq!(at.read("xx 01"), generate(10, 51)); +} + +#[test] +fn test_suffix_with_alternative_precision_lower_hex() { + let (at, mut ucmd) = at_and_ucmd!(); + ucmd.args(&["numbers50.txt", "10", "--suffix", "%#6.3x"]) + .succeeds() + .stdout_only("18\n123\n"); + + let count = glob(&at.plus_as_string("xx*")) + .expect("there should be splits created") + .count(); + + assert_eq!(count, 2); + assert_eq!(at.read("xx 000"), generate(1, 10)); + assert_eq!(at.read("xx 0x001"), generate(10, 51)); +} + +#[test] +fn test_suffix_with_alternative_precision_upper_hex() { + let (at, mut ucmd) = at_and_ucmd!(); + ucmd.args(&["numbers50.txt", "10", "--suffix", "%#6.3X"]) + .succeeds() + .stdout_only("18\n123\n"); + + let count = glob(&at.plus_as_string("xx*")) + .expect("there should be splits created") + .count(); + + assert_eq!(count, 2); + assert_eq!(at.read("xx 000"), generate(1, 10)); + assert_eq!(at.read("xx 0x001"), generate(10, 51)); +}