Merge pull request #8681 from cakebaker/seq_separator_non_utf8

seq: support non-utf8 for `--separator` & `--terminator`
This commit is contained in:
Sylvestre Ledru
2025-09-24 08:03:21 +02:00
committed by GitHub
2 changed files with 65 additions and 20 deletions
+19 -20
View File
@@ -3,7 +3,7 @@
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
// spell-checker:ignore (ToDO) bigdecimal extendedbigdecimal numberparse hexadecimalfloat biguint
use std::ffi::OsString;
use std::ffi::{OsStr, OsString};
use std::io::{BufWriter, ErrorKind, Write, stdout};
use clap::{Arg, ArgAction, Command};
@@ -39,8 +39,8 @@ const ARG_NUMBERS: &str = "numbers";
#[derive(Clone)]
struct SeqOptions<'a> {
separator: String,
terminator: String,
separator: OsString,
terminator: OsString,
equal_width: bool,
format: Option<&'a str>,
}
@@ -105,14 +105,11 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let options = SeqOptions {
separator: matches
.get_one::<String>(OPT_SEPARATOR)
.map_or("\n", |s| s.as_str())
.to_string(),
.get_one::<OsString>(OPT_SEPARATOR)
.map_or(OsString::from("\n"), |s| s.to_os_string()),
terminator: matches
.get_one::<String>(OPT_TERMINATOR)
.map(|s| s.as_str())
.unwrap_or("\n")
.to_string(),
.get_one::<OsString>(OPT_TERMINATOR)
.map_or(OsString::from("\n"), |s| s.to_os_string()),
equal_width: matches.get_flag(OPT_EQUAL_WIDTH),
format: matches.get_one::<String>(OPT_FORMAT).map(|s| s.as_str()),
};
@@ -229,13 +226,15 @@ pub fn uu_app() -> Command {
Arg::new(OPT_SEPARATOR)
.short('s')
.long("separator")
.help(translate!("seq-help-separator")),
.help(translate!("seq-help-separator"))
.value_parser(clap::value_parser!(OsString)),
)
.arg(
Arg::new(OPT_TERMINATOR)
.short('t')
.long("terminator")
.help(translate!("seq-help-terminator")),
.help(translate!("seq-help-terminator"))
.value_parser(clap::value_parser!(OsString)),
)
.arg(
Arg::new(OPT_EQUAL_WIDTH)
@@ -267,8 +266,8 @@ fn fast_print_seq(
first: &BigUint,
increment: u64,
last: &BigUint,
separator: &str,
terminator: &str,
separator: &OsStr,
terminator: &OsStr,
padding: usize,
) -> std::io::Result<()> {
// Nothing to do, just return.
@@ -305,7 +304,7 @@ fn fast_print_seq(
// Initialize buf with first and separator.
buf[start..num_end].copy_from_slice(first_str.as_bytes());
buf[num_end..].copy_from_slice(separator.as_bytes());
buf[num_end..].copy_from_slice(separator.as_encoded_bytes());
// Normally, if padding is > 0, it should be equal to last_length,
// so start would be == 0, but there are corner cases.
@@ -321,7 +320,7 @@ fn fast_print_seq(
}
// Write the last number without separator, but with terminator.
stdout.write_all(&buf[start..num_end])?;
write!(stdout, "{terminator}")?;
stdout.write_all(terminator.as_encoded_bytes())?;
stdout.flush()?;
Ok(())
}
@@ -337,8 +336,8 @@ fn done_printing<T: Zero + PartialOrd>(next: &T, increment: &T, last: &T) -> boo
/// Arbitrary precision decimal number code path ("slow" path)
fn print_seq(
range: RangeFloat,
separator: &str,
terminator: &str,
separator: &OsStr,
terminator: &OsStr,
format: &Format<num_format::Float, &ExtendedBigDecimal>,
fast_allowed: bool,
padding: usize, // Used by fast path only
@@ -375,7 +374,7 @@ fn print_seq(
let mut is_first_iteration = true;
while !done_printing(&value, &increment, &last) {
if !is_first_iteration {
stdout.write_all(separator.as_bytes())?;
stdout.write_all(separator.as_encoded_bytes())?;
}
format.fmt(&mut stdout, &value)?;
// TODO Implement augmenting addition.
@@ -383,7 +382,7 @@ fn print_seq(
is_first_iteration = false;
}
if !is_first_iteration {
stdout.write_all(terminator.as_bytes())?;
stdout.write_all(terminator.as_encoded_bytes())?;
}
stdout.flush()?;
Ok(())
+46
View File
@@ -228,6 +228,52 @@ fn test_separator_and_terminator() {
.stdout_is("2\\n3\\n4\\n5\\n6\n");
}
#[test]
#[cfg(target_os = "linux")]
fn test_separator_non_utf8() {
use std::{ffi::OsString, os::unix::ffi::OsStringExt};
fn create_arg(prefix: &[u8]) -> OsString {
let separator = [0xFF, 0xFE];
OsString::from_vec([prefix, &separator].concat())
}
let short = create_arg(b"-s");
let long = create_arg(b"--separator=");
let expected = [b'1', 0xFF, 0xFE, b'2', b'\n'];
for arg in [short, long] {
new_ucmd!()
.arg(&arg)
.arg("2")
.succeeds()
.stdout_is_bytes(expected);
}
}
#[test]
#[cfg(target_os = "linux")]
fn test_terminator_non_utf8() {
use std::{ffi::OsString, os::unix::ffi::OsStringExt};
fn create_arg(prefix: &[u8]) -> OsString {
let terminator = [0xFF, 0xFE];
OsString::from_vec([prefix, &terminator].concat())
}
let short = create_arg(b"-t");
let long = create_arg(b"--terminator=");
let expected = [b'1', b'\n', b'2', 0xFF, 0xFE];
for arg in [short, long] {
new_ucmd!()
.arg(&arg)
.arg("2")
.succeeds()
.stdout_is_bytes(expected);
}
}
#[test]
fn test_equalize_widths() {
let args = ["-w", "--equal-width"];