From a45ea665935433893f81ebff6fa56ab4fa351b70 Mon Sep 17 00:00:00 2001 From: Simon Legner Date: Sat, 29 Jul 2023 22:19:40 +0200 Subject: [PATCH 01/12] Extract uucore::line_ending::LineEnding Aims to provide consistent newline/zero terminator handling. --- src/uu/comm/src/comm.rs | 33 +------------ src/uu/cut/src/cut.rs | 18 ++++--- src/uu/env/src/env.rs | 15 +++--- src/uu/head/src/head.rs | 68 ++++++++++---------------- src/uu/id/src/id.rs | 9 +--- src/uu/join/src/join.rs | 12 +---- src/uu/ls/src/ls.rs | 17 +++---- src/uu/paste/src/paste.rs | 24 +-------- src/uu/readlink/src/readlink.rs | 18 +++---- src/uu/realpath/src/realpath.rs | 10 ++-- src/uu/sort/src/check.rs | 6 +-- src/uu/sort/src/ext_sort.rs | 6 +-- src/uu/sort/src/merge.rs | 6 +-- src/uu/sort/src/sort.rs | 14 +++--- src/uucore/src/lib/lib.rs | 1 + src/uucore/src/lib/mods.rs | 1 + src/uucore/src/lib/mods/line_ending.rs | 42 ++++++++++++++++ 17 files changed, 126 insertions(+), 174 deletions(-) create mode 100644 src/uucore/src/lib/mods/line_ending.rs diff --git a/src/uu/comm/src/comm.rs b/src/uu/comm/src/comm.rs index 26e70403793..088a921eb5e 100644 --- a/src/uu/comm/src/comm.rs +++ b/src/uu/comm/src/comm.rs @@ -13,6 +13,7 @@ use std::fs::File; use std::io::{self, stdin, BufRead, BufReader, Stdin}; use std::path::Path; use uucore::error::{FromIo, UResult}; +use uucore::line_ending::LineEnding; use uucore::{format_usage, help_about, help_usage}; use clap::{crate_version, Arg, ArgAction, ArgMatches, Command}; @@ -40,38 +41,6 @@ fn column_width(col: &str, opts: &ArgMatches) -> usize { } } -#[repr(u8)] -#[derive(Clone, Copy)] -enum LineEnding { - Newline = b'\n', - Nul = 0, -} - -impl From for u8 { - fn from(line_ending: LineEnding) -> Self { - line_ending as Self - } -} - -impl From for LineEnding { - fn from(is_zero_terminated: bool) -> Self { - if is_zero_terminated { - Self::Nul - } else { - Self::Newline - } - } -} - -impl Display for LineEnding { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Newline => writeln!(f), - Self::Nul => write!(f, "\0"), - } - } -} - enum Input { Stdin(Stdin), FileIn(BufReader), diff --git a/src/uu/cut/src/cut.rs b/src/uu/cut/src/cut.rs index 68ad566d7c4..0251a3c7752 100644 --- a/src/uu/cut/src/cut.rs +++ b/src/uu/cut/src/cut.rs @@ -10,6 +10,7 @@ use bstr::io::BufReadExt; use clap::{crate_version, Arg, ArgAction, Command}; use is_terminal::IsTerminal; +use uucore::line_ending::LineEnding; use std::fs::File; use std::io::{stdin, stdout, BufReader, BufWriter, Read, Write}; use std::path::Path; @@ -30,7 +31,7 @@ const AFTER_HELP: &str = help_section!("after help", "cut.md"); struct Options { out_delim: Option, - zero_terminated: bool, + line_ending: LineEnding, } enum Delimiter { @@ -42,7 +43,7 @@ struct FieldOptions { delimiter: Delimiter, out_delimiter: Option, only_delimited: bool, - zero_terminated: bool, + line_ending: LineEnding, } enum Mode { @@ -68,7 +69,7 @@ fn list_to_ranges(list: &str, complement: bool) -> Result, String> { } fn cut_bytes(reader: R, ranges: &[Range], opts: &Options) -> UResult<()> { - let newline_char = if opts.zero_terminated { b'\0' } else { b'\n' }; + let newline_char = opts.line_ending.into(); let mut buf_in = BufReader::new(reader); let mut out = stdout_writer(); let delim = opts @@ -259,7 +260,7 @@ fn cut_fields_implicit_out_delim( } fn cut_fields(reader: R, ranges: &[Range], opts: &FieldOptions) -> UResult<()> { - let newline_char = if opts.zero_terminated { b'\0' } else { b'\n' }; + let newline_char = opts.line_ending.into(); match opts.delimiter { Delimiter::String(ref delim) => { let matcher = ExactMatcher::new(delim.as_bytes()); @@ -376,7 +377,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { .unwrap_or_default() .to_owned(), ), - zero_terminated: matches.get_flag(options::ZERO_TERMINATED), + line_ending: LineEnding::from(matches.get_flag(options::ZERO_TERMINATED)), }, ) }), @@ -391,7 +392,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { .unwrap_or_default() .to_owned(), ), - zero_terminated: matches.get_flag(options::ZERO_TERMINATED), + line_ending: LineEnding::from(matches.get_flag(options::ZERO_TERMINATED)), }, ) }), @@ -411,6 +412,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let only_delimited = matches.get_flag(options::ONLY_DELIMITED); let whitespace_delimited = matches.get_flag(options::WHITESPACE_DELIMITED); let zero_terminated = matches.get_flag(options::ZERO_TERMINATED); + let line_ending = LineEnding::from(zero_terminated); match matches.get_one::(options::DELIMITER).map(|s| s.as_str()) { Some(_) if whitespace_delimited => { @@ -441,7 +443,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { delimiter: Delimiter::String(delim), out_delimiter: out_delim, only_delimited, - zero_terminated, + line_ending, }, )) } @@ -455,7 +457,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { }, out_delimiter: out_delim, only_delimited, - zero_terminated, + line_ending, }, )), } diff --git a/src/uu/env/src/env.rs b/src/uu/env/src/env.rs index b293bc9bf66..f00cf695a8a 100644 --- a/src/uu/env/src/env.rs +++ b/src/uu/env/src/env.rs @@ -23,6 +23,7 @@ use std::os::unix::process::ExitStatusExt; use std::process; use uucore::display::Quotable; use uucore::error::{UClapError, UResult, USimpleError, UUsageError}; +use uucore::line_ending::LineEnding; use uucore::{format_usage, help_about, help_section, help_usage, show_warning}; const ABOUT: &str = help_about!("env.md"); @@ -31,7 +32,7 @@ const AFTER_HELP: &str = help_section!("after help", "env.md"); struct Options<'a> { ignore_env: bool, - null: bool, + line_ending: LineEnding, running_directory: Option<&'a str>, files: Vec<&'a str>, unsets: Vec<&'a str>, @@ -41,11 +42,11 @@ struct Options<'a> { // print name=value env pairs on screen // if null is true, separate pairs with a \0, \n otherwise -fn print_env(null: bool) { +fn print_env(line_ending: LineEnding) { let stdout_raw = io::stdout(); let mut stdout = stdout_raw.lock(); for (n, v) in env::vars() { - write!(stdout, "{}={}{}", n, v, if null { '\0' } else { '\n' }).unwrap(); + write!(stdout, "{}={}{}", n, v, line_ending).unwrap(); } } @@ -64,7 +65,7 @@ fn parse_name_value_opt<'a>(opts: &mut Options<'a>, opt: &'a str) -> UResult(opts: &mut Options<'a>, opt: &'a str) -> UResult<()> { - if opts.null { + if opts.line_ending == LineEnding::Nul { Err(UUsageError::new( 125, "cannot specify --null (-0) with command".to_string(), @@ -181,7 +182,7 @@ fn run_env(args: impl uucore::Args) -> UResult<()> { let matches = app.try_get_matches_from(args).with_exit_code(125)?; let ignore_env = matches.get_flag("ignore-environment"); - let null = matches.get_flag("null"); + let line_ending = LineEnding::from(matches.get_flag("null")); let running_directory = matches.get_one::("chdir").map(|s| s.as_str()); let files = match matches.get_many::("file") { Some(v) => v.map(|s| s.as_str()).collect(), @@ -194,7 +195,7 @@ fn run_env(args: impl uucore::Args) -> UResult<()> { let mut opts = Options { ignore_env, - null, + line_ending, running_directory, files, unsets, @@ -302,7 +303,7 @@ fn run_env(args: impl uucore::Args) -> UResult<()> { if opts.program.is_empty() { // no program provided, so just dump all env vars to stdout - print_env(opts.null); + print_env(opts.line_ending); } else { // we need to execute a command let (prog, args) = build_command(&mut opts.program); diff --git a/src/uu/head/src/head.rs b/src/uu/head/src/head.rs index 2517a98ea45..84eaab34278 100644 --- a/src/uu/head/src/head.rs +++ b/src/uu/head/src/head.rs @@ -6,6 +6,7 @@ // spell-checker:ignore (vars) zlines BUFWRITER seekable use clap::{crate_version, Arg, ArgAction, ArgMatches, Command}; +use uucore::line_ending::LineEnding; use std::ffi::OsString; use std::io::{self, BufWriter, ErrorKind, Read, Seek, SeekFrom, Write}; use uucore::display::Quotable; @@ -184,7 +185,7 @@ fn arg_iterate<'a>( struct HeadOptions { pub quiet: bool, pub verbose: bool, - pub zeroed: bool, + pub line_ending: LineEnding, pub presume_input_pipe: bool, pub mode: Mode, pub files: Vec, @@ -197,7 +198,7 @@ impl HeadOptions { options.quiet = matches.get_flag(options::QUIET_NAME); options.verbose = matches.get_flag(options::VERBOSE_NAME); - options.zeroed = matches.get_flag(options::ZERO_NAME); + options.line_ending = LineEnding::from(matches.get_flag(options::ZERO_NAME)); options.presume_input_pipe = matches.get_flag(options::PRESUME_INPUT_PIPE); options.mode = Mode::from(matches)?; @@ -227,9 +228,8 @@ where Ok(()) } -fn read_n_lines(input: &mut impl std::io::BufRead, n: u64, zero: bool) -> std::io::Result<()> { +fn read_n_lines(input: &mut impl std::io::BufRead, n: u64, separator: u8) -> std::io::Result<()> { // Read the first `n` lines from the `input` reader. - let separator = if zero { b'\0' } else { b'\n' }; let mut reader = take_lines(input, n, separator); // Write those bytes to `stdout`. @@ -293,20 +293,12 @@ fn read_but_last_n_bytes(input: &mut impl std::io::BufRead, n: usize) -> std::io fn read_but_last_n_lines( input: impl std::io::BufRead, n: usize, - zero: bool, + separator: u8, ) -> std::io::Result<()> { - if zero { - let stdout = std::io::stdout(); - let mut stdout = stdout.lock(); - for bytes in take_all_but(lines(input, b'\0'), n) { - stdout.write_all(&bytes?)?; - } - } else { - let stdout = std::io::stdout(); - let mut stdout = stdout.lock(); - for bytes in take_all_but(lines(input, b'\n'), n) { - stdout.write_all(&bytes?)?; - } + let stdout = std::io::stdout(); + let mut stdout = stdout.lock(); + for bytes in take_all_but(lines(input, separator), n) { + stdout.write_all(&bytes?)?; } Ok(()) } @@ -350,7 +342,7 @@ fn read_but_last_n_lines( /// assert_eq!(find_nth_line_from_end(&mut input, 4, false).unwrap(), 0); /// assert_eq!(find_nth_line_from_end(&mut input, 1000, false).unwrap(), 0); /// ``` -fn find_nth_line_from_end(input: &mut R, n: u64, zeroed: bool) -> std::io::Result +fn find_nth_line_from_end(input: &mut R, n: u64, separator: u8) -> std::io::Result where R: Read + Seek, { @@ -370,14 +362,8 @@ where ))?; input.read_exact(buffer)?; for byte in buffer.iter().rev() { - match byte { - b'\n' if !zeroed => { - lines += 1; - } - 0u8 if zeroed => { - lines += 1; - } - _ => {} + if byte == &separator { + lines += 1; } // if it were just `n`, if lines == n + 1 { @@ -407,7 +393,7 @@ fn head_backwards_file(input: &mut std::fs::File, options: &HeadOptions) -> std: } } Mode::AllButLastLines(n) => { - let found = find_nth_line_from_end(input, n, options.zeroed)?; + let found = find_nth_line_from_end(input, n, options.line_ending.into())?; read_n_bytes( &mut std::io::BufReader::with_capacity(BUF_SIZE, input), found, @@ -426,7 +412,7 @@ fn head_file(input: &mut std::fs::File, options: &HeadOptions) -> std::io::Resul Mode::FirstLines(n) => read_n_lines( &mut std::io::BufReader::with_capacity(BUF_SIZE, input), n, - options.zeroed, + options.line_ending.into(), ), Mode::AllButLastBytes(_) | Mode::AllButLastLines(_) => head_backwards_file(input, options), } @@ -466,10 +452,10 @@ fn uu_head(options: &HeadOptions) -> UResult<()> { Mode::AllButLastBytes(n) => { read_but_last_n_bytes(&mut stdin, n.try_into().unwrap()) } - Mode::FirstLines(n) => read_n_lines(&mut stdin, n, options.zeroed), + Mode::FirstLines(n) => read_n_lines(&mut stdin, n, options.line_ending.into()), // unwrap is guaranteed to succeed because we checked the value of n above Mode::AllButLastLines(n) => { - read_but_last_n_lines(&mut stdin, n.try_into().unwrap(), options.zeroed) + read_but_last_n_lines(&mut stdin, n.try_into().unwrap(), options.line_ending.into()) } } } @@ -541,7 +527,7 @@ mod tests { #[test] fn test_args_modes() { let args = options("-n -10M -vz").unwrap(); - assert!(args.zeroed); + assert!(args.line_ending == LineEnding::Nul); assert!(args.verbose); assert_eq!(args.mode, Mode::AllButLastLines(10 * 1024 * 1024)); } @@ -561,8 +547,8 @@ mod tests { assert!(options("-q").unwrap().quiet); assert!(options("--verbose").unwrap().verbose); assert!(options("-v").unwrap().verbose); - assert!(options("--zero-terminated").unwrap().zeroed); - assert!(options("-z").unwrap().zeroed); + assert!(options("--zero-terminated").unwrap().line_ending == LineEnding::Nul); + assert!(options("-z").unwrap().line_ending == LineEnding::Nul); assert_eq!(options("--lines 15").unwrap().mode, Mode::FirstLines(15)); assert_eq!(options("-n 15").unwrap().mode, Mode::FirstLines(15)); assert_eq!(options("--bytes 15").unwrap().mode, Mode::FirstBytes(15)); @@ -579,7 +565,7 @@ mod tests { assert!(!opts.verbose); assert!(!opts.quiet); - assert!(!opts.zeroed); + assert!(opts.line_ending == LineEnding::Newline); assert_eq!(opts.mode, Mode::FirstLines(10)); assert!(opts.files.is_empty()); } @@ -631,17 +617,17 @@ mod tests { fn read_early_exit() { let mut empty = std::io::BufReader::new(std::io::Cursor::new(Vec::new())); assert!(read_n_bytes(&mut empty, 0).is_ok()); - assert!(read_n_lines(&mut empty, 0, false).is_ok()); + assert!(read_n_lines(&mut empty, 0, b'\n').is_ok()); } #[test] fn test_find_nth_line_from_end() { let mut input = Cursor::new("x\ny\nz\n"); - assert_eq!(find_nth_line_from_end(&mut input, 0, false).unwrap(), 6); - assert_eq!(find_nth_line_from_end(&mut input, 1, false).unwrap(), 4); - assert_eq!(find_nth_line_from_end(&mut input, 2, false).unwrap(), 2); - assert_eq!(find_nth_line_from_end(&mut input, 3, false).unwrap(), 0); - assert_eq!(find_nth_line_from_end(&mut input, 4, false).unwrap(), 0); - assert_eq!(find_nth_line_from_end(&mut input, 1000, false).unwrap(), 0); + assert_eq!(find_nth_line_from_end(&mut input, 0, b'\n').unwrap(), 6); + assert_eq!(find_nth_line_from_end(&mut input, 1, b'\n').unwrap(), 4); + assert_eq!(find_nth_line_from_end(&mut input, 2, b'\n').unwrap(), 2); + assert_eq!(find_nth_line_from_end(&mut input, 3, b'\n').unwrap(), 0); + assert_eq!(find_nth_line_from_end(&mut input, 4, b'\n').unwrap(), 0); + assert_eq!(find_nth_line_from_end(&mut input, 1000, b'\n').unwrap(), 0); } } diff --git a/src/uu/id/src/id.rs b/src/uu/id/src/id.rs index 7a8e40059be..52b0750e0f7 100644 --- a/src/uu/id/src/id.rs +++ b/src/uu/id/src/id.rs @@ -44,6 +44,7 @@ use uucore::error::UResult; use uucore::error::{set_exit_code, USimpleError}; pub use uucore::libc; use uucore::libc::{getlogin, uid_t}; +use uucore::line_ending::LineEnding; use uucore::process::{getegid, geteuid, getgid, getuid}; use uucore::{format_usage, help_about, help_section, help_usage, show_error}; @@ -174,13 +175,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { " ".to_string() } }; - let line_ending = { - if state.zflag { - '\0' - } else { - '\n' - } - }; + let line_ending = LineEnding::from(state.zflag); if state.cflag { if state.selinux_supported { diff --git a/src/uu/join/src/join.rs b/src/uu/join/src/join.rs index de1a9181be7..692a8277c77 100644 --- a/src/uu/join/src/join.rs +++ b/src/uu/join/src/join.rs @@ -22,6 +22,7 @@ use std::num::IntErrorKind; use std::os::unix::ffi::OsStrExt; use uucore::display::Quotable; use uucore::error::{set_exit_code, UError, UResult, USimpleError}; +use uucore::line_ending::LineEnding; use uucore::{crash, crash_if_err, format_usage, help_about, help_usage}; const ABOUT: &str = help_about!("join.md"); @@ -62,13 +63,6 @@ enum FileNum { File2, } -#[repr(u8)] -#[derive(Copy, Clone)] -enum LineEnding { - Nul = 0, - Newline = b'\n', -} - #[derive(Copy, Clone, PartialEq)] enum Sep { Char(u8), @@ -683,9 +677,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { settings.headers = true; } - if matches.get_flag("z") { - settings.line_ending = LineEnding::Nul; - } + settings.line_ending = LineEnding::from(matches.get_flag("z")); let file1 = matches.get_one::("file1").unwrap(); let file2 = matches.get_one::("file2").unwrap(); diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index cead745cd54..dc297bcb024 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -54,6 +54,7 @@ use unicode_width::UnicodeWidthStr; use uucore::libc::{dev_t, major, minor}; #[cfg(unix)] use uucore::libc::{S_IXGRP, S_IXOTH, S_IXUSR}; +use uucore::line_ending::LineEnding; use uucore::quoting_style::{escape_name, QuotingStyle}; use uucore::{ display::Quotable, @@ -408,7 +409,7 @@ pub struct Config { context: bool, selinux_supported: bool, group_directories_first: bool, - eol: char, + line_ending: LineEnding, } // Fields that can be removed or added to the long format @@ -1005,11 +1006,7 @@ impl Config { } }, group_directories_first: options.get_flag(options::GROUP_DIRECTORIES_FIRST), - eol: if options.get_flag(options::ZERO) { - '\0' - } else { - '\n' - }, + line_ending: LineEnding::from(options.get_flag(options::ZERO)), }) } } @@ -2173,7 +2170,7 @@ fn display_total(items: &[PathData], config: &Config, out: &mut BufWriter 0 { - write!(out, "{}", config.eol)?; + write!(out, "{}", config.line_ending)?; } } _ => { for name in names { - write!(out, "{}{}", name.contents, config.eol)?; + write!(out, "{}{}", name.contents, config.line_ending)?; } } }; @@ -2491,7 +2488,7 @@ fn display_item_long( let dfn = display_file_name(item, config, None, String::new(), out).contents; - write!(out, " {} {}{}", display_date(md, config), dfn, config.eol)?; + write!(out, " {} {}{}", display_date(md, config), dfn, config.line_ending)?; } else { #[cfg(unix)] let leading_char = { diff --git a/src/uu/paste/src/paste.rs b/src/uu/paste/src/paste.rs index 6e52727f7ce..f5e95e821b5 100644 --- a/src/uu/paste/src/paste.rs +++ b/src/uu/paste/src/paste.rs @@ -8,11 +8,11 @@ // spell-checker:ignore (ToDO) delim use clap::{crate_version, Arg, ArgAction, Command}; -use std::fmt::Display; use std::fs::File; use std::io::{stdin, stdout, BufRead, BufReader, Read, Write}; use std::path::Path; use uucore::error::{FromIo, UResult, USimpleError}; +use uucore::line_ending::LineEnding; use uucore::{format_usage, help_about, help_usage}; const ABOUT: &str = help_about!("paste.md"); @@ -25,22 +25,6 @@ mod options { pub const ZERO_TERMINATED: &str = "zero-terminated"; } -#[repr(u8)] -#[derive(Clone, Copy)] -enum LineEnding { - Newline = b'\n', - Nul = 0, -} - -impl Display for LineEnding { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Newline => writeln!(f), - Self::Nul => write!(f, "\0"), - } - } -} - // Wraps BufReader and stdin fn read_until( reader: Option<&mut BufReader>, @@ -64,11 +48,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { .unwrap() .map(|s| s.to_owned()) .collect(); - let line_ending = if matches.get_flag(options::ZERO_TERMINATED) { - LineEnding::Nul - } else { - LineEnding::Newline - }; + let line_ending = LineEnding::from(matches.get_flag(options::ZERO_TERMINATED)); paste(files, serial, delimiters, line_ending) } diff --git a/src/uu/readlink/src/readlink.rs b/src/uu/readlink/src/readlink.rs index e247c614695..2fbbabac10a 100644 --- a/src/uu/readlink/src/readlink.rs +++ b/src/uu/readlink/src/readlink.rs @@ -15,6 +15,7 @@ use uucore::display::Quotable; use uucore::error::{FromIo, UResult, USimpleError, UUsageError}; use uucore::fs::{canonicalize, MissingHandling, ResolveMode}; use uucore::{format_usage, help_about, help_usage, show_error}; +use uucore::line_ending::LineEnding; const ABOUT: &str = help_about!("readlink.md"); const USAGE: &str = help_usage!("readlink.md"); @@ -67,6 +68,11 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { show_error!("ignoring --no-newline with multiple arguments"); no_trailing_delimiter = false; } + let line_ending = if no_trailing_delimiter { + LineEnding::None + } else { + LineEnding::from(use_zero) + }; for f in &files { let p = PathBuf::from(f); @@ -77,7 +83,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { }; match path_result { Ok(path) => { - show(&path, no_trailing_delimiter, use_zero).map_err_context(String::new)?; + show(&path, line_ending).map_err_context(String::new)?; } Err(err) => { if verbose { @@ -173,14 +179,8 @@ pub fn uu_app() -> Command { ) } -fn show(path: &Path, no_trailing_delimiter: bool, use_zero: bool) -> std::io::Result<()> { +fn show(path: &Path, line_ending: LineEnding) -> std::io::Result<()> { let path = path.to_str().unwrap(); - if no_trailing_delimiter { - print!("{path}"); - } else if use_zero { - print!("{path}\0"); - } else { - println!("{path}"); - } + print!("{path}{line_ending}"); stdout().flush() } diff --git a/src/uu/realpath/src/realpath.rs b/src/uu/realpath/src/realpath.rs index cb7a09a41f3..6afd12787ff 100644 --- a/src/uu/realpath/src/realpath.rs +++ b/src/uu/realpath/src/realpath.rs @@ -21,6 +21,7 @@ use uucore::{ format_usage, fs::{canonicalize, MissingHandling, ResolveMode}, help_about, help_usage, + line_ending::LineEnding, }; use uucore::{error::UClapError, show, show_if_err}; @@ -52,7 +53,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { .collect(); let strip = matches.get_flag(OPT_STRIP); - let zero = matches.get_flag(OPT_ZERO); + let line_ending = LineEnding::from(matches.get_flag(OPT_ZERO)); let quiet = matches.get_flag(OPT_QUIET); let logical = matches.get_flag(OPT_LOGICAL); let can_mode = if matches.get_flag(OPT_CANONICALIZE_EXISTING) { @@ -73,7 +74,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { for path in &paths { let result = resolve_path( path, - zero, + line_ending, resolve_mode, can_mode, relative_to.as_deref(), @@ -249,19 +250,18 @@ fn canonicalize_relative( /// symbolic links. fn resolve_path( p: &Path, - zero: bool, + line_ending: LineEnding, resolve: ResolveMode, can_mode: MissingHandling, relative_to: Option<&Path>, relative_base: Option<&Path>, ) -> std::io::Result<()> { let abs = canonicalize(p, can_mode, resolve)?; - let line_ending = if zero { b'\0' } else { b'\n' }; let abs = process_relative(abs, relative_base, relative_to); print_verbatim(abs)?; - stdout().write_all(&[line_ending])?; + stdout().write_all(&[line_ending.into()])?; Ok(()) } diff --git a/src/uu/sort/src/check.rs b/src/uu/sort/src/check.rs index 3f02a4c3153..700e3681ca4 100644 --- a/src/uu/sort/src/check.rs +++ b/src/uu/sort/src/check.rs @@ -115,11 +115,7 @@ fn reader( &mut carry_over, &mut file, &mut iter::empty(), - if settings.zero_terminated { - b'\0' - } else { - b'\n' - }, + settings.line_ending.into(), settings, )?; if !should_continue { diff --git a/src/uu/sort/src/ext_sort.rs b/src/uu/sort/src/ext_sort.rs index 27cb12d0bb0..a8f4b2590a3 100644 --- a/src/uu/sort/src/ext_sort.rs +++ b/src/uu/sort/src/ext_sort.rs @@ -84,11 +84,7 @@ fn reader_writer< output: Output, tmp_dir: &mut TmpDirWrapper, ) -> UResult<()> { - let separator = if settings.zero_terminated { - b'\0' - } else { - b'\n' - }; + let separator = settings.line_ending.into(); // Heuristically chosen: Dividing by 10 seems to keep our memory usage roughly // around settings.buffer_size as a whole. diff --git a/src/uu/sort/src/merge.rs b/src/uu/sort/src/merge.rs index 7c682d88f11..1a987fb4eec 100644 --- a/src/uu/sort/src/merge.rs +++ b/src/uu/sort/src/merge.rs @@ -169,11 +169,7 @@ fn merge_without_limit>>( &request_receiver, &mut reader_files, &settings, - if settings.zero_terminated { - b'\0' - } else { - b'\n' - }, + settings.line_ending.into(), ) } }); diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index 80c2275e969..75277a42672 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -30,6 +30,7 @@ use fnv::FnvHasher; use numeric_str_cmp::{human_numeric_str_cmp, numeric_str_cmp, NumInfo, NumInfoParseSettings}; use rand::{thread_rng, Rng}; use rayon::prelude::*; +use uucore::line_ending::LineEnding; use std::cmp::Ordering; use std::env; use std::error::Error; @@ -306,7 +307,7 @@ pub struct GlobalSettings { selectors: Vec, separator: Option, threads: String, - zero_terminated: bool, + line_ending: LineEnding, buffer_size: usize, compress_prog: Option, merge_batch_size: usize, @@ -383,7 +384,7 @@ impl Default for GlobalSettings { selectors: vec![], separator: None, threads: String::new(), - zero_terminated: false, + line_ending: LineEnding::Newline, buffer_size: DEFAULT_BUF_SIZE, compress_prog: None, merge_batch_size: 32, @@ -526,12 +527,9 @@ impl<'a> Line<'a> { } fn print(&self, writer: &mut impl Write, settings: &GlobalSettings) { - if settings.zero_terminated && !settings.debug { + if !settings.debug { writer.write_all(self.line.as_bytes()).unwrap(); - writer.write_all(b"\0").unwrap(); - } else if !settings.debug { - writer.write_all(self.line.as_bytes()).unwrap(); - writer.write_all(b"\n").unwrap(); + writer.write(&[settings.line_ending.into()]).unwrap(); } else { self.print_debug(settings, writer).unwrap(); } @@ -1169,7 +1167,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { })?; } - settings.zero_terminated = matches.get_flag(options::ZERO_TERMINATED); + settings.line_ending = LineEnding::from(matches.get_flag(options::ZERO_TERMINATED)); settings.merge = matches.get_flag(options::MERGE); settings.check = matches.contains_id(options::check::CHECK); diff --git a/src/uucore/src/lib/lib.rs b/src/uucore/src/lib/lib.rs index ca9a48d258a..74fb9d4e3b2 100644 --- a/src/uucore/src/lib/lib.rs +++ b/src/uucore/src/lib/lib.rs @@ -22,6 +22,7 @@ pub use uucore_procs::*; pub use crate::mods::backup_control; pub use crate::mods::display; pub use crate::mods::error; +pub use crate::mods::line_ending; pub use crate::mods::os; pub use crate::mods::panic; pub use crate::mods::quoting_style; diff --git a/src/uucore/src/lib/mods.rs b/src/uucore/src/lib/mods.rs index 71d288c69a5..52da2788a48 100644 --- a/src/uucore/src/lib/mods.rs +++ b/src/uucore/src/lib/mods.rs @@ -3,6 +3,7 @@ pub mod backup_control; pub mod display; pub mod error; +pub mod line_ending; pub mod os; pub mod panic; pub mod ranges; diff --git a/src/uucore/src/lib/mods/line_ending.rs b/src/uucore/src/lib/mods/line_ending.rs new file mode 100644 index 00000000000..52d5e2f48d5 --- /dev/null +++ b/src/uucore/src/lib/mods/line_ending.rs @@ -0,0 +1,42 @@ +/// Aims to provide consistent newline/zero terminator handling +use std::fmt::Display; + +#[repr(u8)] +#[derive(Clone, Copy, Debug, Default, PartialEq)] +/// Line terminator used for printing and parsing +pub enum LineEnding { + #[default] Newline = b'\n', + Nul = 0, + Space = b' ', + None = 8, // abuse backspace \b to encode None +} + +impl Display for LineEnding { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Newline => writeln!(f), + Self::Nul => write!(f, "\0"), + Self::Space => write!(f, " "), + Self::None => std::fmt::Result::Ok(()), + } + } +} + +impl From for u8 { + fn from(line_ending: LineEnding) -> Self { + match line_ending { + LineEnding::None => panic!("Cannot convert LineEnding::None to u8"), + _ => line_ending as Self, + } + } +} + +impl From for LineEnding { + fn from(is_zero_terminated: bool) -> Self { + if is_zero_terminated { + Self::Nul + } else { + Self::Newline + } + } +} From a3855dd89a1d07fca710a7b15a052d0cb77efb82 Mon Sep 17 00:00:00 2001 From: Simon Legner Date: Sun, 30 Jul 2023 10:51:32 +0200 Subject: [PATCH 02/12] Apply suggestions from code review Co-authored-by: Terts Diepraam --- src/uu/head/src/head.rs | 6 +++--- src/uucore/src/lib/mods/line_ending.rs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/uu/head/src/head.rs b/src/uu/head/src/head.rs index 84eaab34278..3ea3bd3038b 100644 --- a/src/uu/head/src/head.rs +++ b/src/uu/head/src/head.rs @@ -527,7 +527,7 @@ mod tests { #[test] fn test_args_modes() { let args = options("-n -10M -vz").unwrap(); - assert!(args.line_ending == LineEnding::Nul); + assert_eq!(args.line_ending, LineEnding::Nul); assert!(args.verbose); assert_eq!(args.mode, Mode::AllButLastLines(10 * 1024 * 1024)); } @@ -547,8 +547,8 @@ mod tests { assert!(options("-q").unwrap().quiet); assert!(options("--verbose").unwrap().verbose); assert!(options("-v").unwrap().verbose); - assert!(options("--zero-terminated").unwrap().line_ending == LineEnding::Nul); - assert!(options("-z").unwrap().line_ending == LineEnding::Nul); + assert_eq!(options("--zero-terminated").unwrap().line_ending, LineEnding::Nul); + assert_eq!(options("-z").unwrap().line_ending, LineEnding::Nul); assert_eq!(options("--lines 15").unwrap().mode, Mode::FirstLines(15)); assert_eq!(options("-n 15").unwrap().mode, Mode::FirstLines(15)); assert_eq!(options("--bytes 15").unwrap().mode, Mode::FirstBytes(15)); diff --git a/src/uucore/src/lib/mods/line_ending.rs b/src/uucore/src/lib/mods/line_ending.rs index 52d5e2f48d5..a61a6256aa8 100644 --- a/src/uucore/src/lib/mods/line_ending.rs +++ b/src/uucore/src/lib/mods/line_ending.rs @@ -1,4 +1,4 @@ -/// Aims to provide consistent newline/zero terminator handling +//! Provides consistent newline/zero terminator handling use std::fmt::Display; #[repr(u8)] From 6a733be4e8b19a79b8311babc47ebcc64f46908a Mon Sep 17 00:00:00 2001 From: Simon Legner Date: Sun, 30 Jul 2023 10:53:46 +0200 Subject: [PATCH 03/12] cargo fmt --- src/uu/cut/src/cut.rs | 2 +- src/uu/head/src/head.rs | 15 ++++++++++----- src/uu/ls/src/ls.rs | 8 +++++++- src/uu/readlink/src/readlink.rs | 2 +- src/uu/sort/src/sort.rs | 2 +- src/uucore/src/lib/mods/line_ending.rs | 3 ++- 6 files changed, 22 insertions(+), 10 deletions(-) diff --git a/src/uu/cut/src/cut.rs b/src/uu/cut/src/cut.rs index 0251a3c7752..aec2df8073f 100644 --- a/src/uu/cut/src/cut.rs +++ b/src/uu/cut/src/cut.rs @@ -10,12 +10,12 @@ use bstr::io::BufReadExt; use clap::{crate_version, Arg, ArgAction, Command}; use is_terminal::IsTerminal; -use uucore::line_ending::LineEnding; use std::fs::File; use std::io::{stdin, stdout, BufReader, BufWriter, Read, Write}; use std::path::Path; use uucore::display::Quotable; use uucore::error::{FromIo, UResult, USimpleError}; +use uucore::line_ending::LineEnding; use self::searcher::Searcher; use matcher::{ExactMatcher, Matcher, WhitespaceMatcher}; diff --git a/src/uu/head/src/head.rs b/src/uu/head/src/head.rs index 3ea3bd3038b..3f2271613d8 100644 --- a/src/uu/head/src/head.rs +++ b/src/uu/head/src/head.rs @@ -6,11 +6,11 @@ // spell-checker:ignore (vars) zlines BUFWRITER seekable use clap::{crate_version, Arg, ArgAction, ArgMatches, Command}; -use uucore::line_ending::LineEnding; use std::ffi::OsString; use std::io::{self, BufWriter, ErrorKind, Read, Seek, SeekFrom, Write}; use uucore::display::Quotable; use uucore::error::{FromIo, UError, UResult, USimpleError}; +use uucore::line_ending::LineEnding; use uucore::lines::lines; use uucore::{format_usage, help_about, help_usage, show}; @@ -454,9 +454,11 @@ fn uu_head(options: &HeadOptions) -> UResult<()> { } Mode::FirstLines(n) => read_n_lines(&mut stdin, n, options.line_ending.into()), // unwrap is guaranteed to succeed because we checked the value of n above - Mode::AllButLastLines(n) => { - read_but_last_n_lines(&mut stdin, n.try_into().unwrap(), options.line_ending.into()) - } + Mode::AllButLastLines(n) => read_but_last_n_lines( + &mut stdin, + n.try_into().unwrap(), + options.line_ending.into(), + ), } } (name, false) => { @@ -547,7 +549,10 @@ mod tests { assert!(options("-q").unwrap().quiet); assert!(options("--verbose").unwrap().verbose); assert!(options("-v").unwrap().verbose); - assert_eq!(options("--zero-terminated").unwrap().line_ending, LineEnding::Nul); + assert_eq!( + options("--zero-terminated").unwrap().line_ending, + LineEnding::Nul + ); assert_eq!(options("-z").unwrap().line_ending, LineEnding::Nul); assert_eq!(options("--lines 15").unwrap().mode, Mode::FirstLines(15)); assert_eq!(options("-n 15").unwrap().mode, Mode::FirstLines(15)); diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index dc297bcb024..98f434e010e 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -2488,7 +2488,13 @@ fn display_item_long( let dfn = display_file_name(item, config, None, String::new(), out).contents; - write!(out, " {} {}{}", display_date(md, config), dfn, config.line_ending)?; + write!( + out, + " {} {}{}", + display_date(md, config), + dfn, + config.line_ending + )?; } else { #[cfg(unix)] let leading_char = { diff --git a/src/uu/readlink/src/readlink.rs b/src/uu/readlink/src/readlink.rs index 2fbbabac10a..77de085b389 100644 --- a/src/uu/readlink/src/readlink.rs +++ b/src/uu/readlink/src/readlink.rs @@ -14,8 +14,8 @@ use std::path::{Path, PathBuf}; use uucore::display::Quotable; use uucore::error::{FromIo, UResult, USimpleError, UUsageError}; use uucore::fs::{canonicalize, MissingHandling, ResolveMode}; -use uucore::{format_usage, help_about, help_usage, show_error}; use uucore::line_ending::LineEnding; +use uucore::{format_usage, help_about, help_usage, show_error}; const ABOUT: &str = help_about!("readlink.md"); const USAGE: &str = help_usage!("readlink.md"); diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index 75277a42672..bb270c6f41f 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -30,7 +30,6 @@ use fnv::FnvHasher; use numeric_str_cmp::{human_numeric_str_cmp, numeric_str_cmp, NumInfo, NumInfoParseSettings}; use rand::{thread_rng, Rng}; use rayon::prelude::*; -use uucore::line_ending::LineEnding; use std::cmp::Ordering; use std::env; use std::error::Error; @@ -46,6 +45,7 @@ use std::str::Utf8Error; use unicode_width::UnicodeWidthStr; use uucore::display::Quotable; use uucore::error::{set_exit_code, strip_errno, UError, UResult, USimpleError, UUsageError}; +use uucore::line_ending::LineEnding; use uucore::parse_size::{ParseSizeError, Parser}; use uucore::version_cmp::version_cmp; use uucore::{format_usage, help_about, help_section, help_usage}; diff --git a/src/uucore/src/lib/mods/line_ending.rs b/src/uucore/src/lib/mods/line_ending.rs index a61a6256aa8..b2e7009f73e 100644 --- a/src/uucore/src/lib/mods/line_ending.rs +++ b/src/uucore/src/lib/mods/line_ending.rs @@ -5,7 +5,8 @@ use std::fmt::Display; #[derive(Clone, Copy, Debug, Default, PartialEq)] /// Line terminator used for printing and parsing pub enum LineEnding { - #[default] Newline = b'\n', + #[default] + Newline = b'\n', Nul = 0, Space = b' ', None = 8, // abuse backspace \b to encode None From 75f18652039edac459b8ba93a51610d3c97980c9 Mon Sep 17 00:00:00 2001 From: Simon Legner Date: Sun, 30 Jul 2023 11:09:10 +0200 Subject: [PATCH 04/12] Use uucore::line_ending::LineEnding --- src/uu/basename/src/basename.rs | 5 +++-- src/uu/dirname/src/dirname.rs | 9 +++------ src/uu/du/src/du.rs | 13 +++++-------- 3 files changed, 11 insertions(+), 16 deletions(-) diff --git a/src/uu/basename/src/basename.rs b/src/uu/basename/src/basename.rs index ed7faee6566..ad9487a4e6f 100644 --- a/src/uu/basename/src/basename.rs +++ b/src/uu/basename/src/basename.rs @@ -11,6 +11,7 @@ use clap::{crate_version, Arg, ArgAction, Command}; use std::path::{is_separator, PathBuf}; use uucore::display::Quotable; use uucore::error::{UResult, UUsageError}; +use uucore::line_ending::LineEnding; use uucore::{format_usage, help_about, help_usage}; static ABOUT: &str = help_about!("basename.md"); @@ -54,9 +55,10 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { return Err(UUsageError::new(1, "missing operand".to_string())); } + let line_ending = LineEnding::from(matches.get_flag(options::ZERO)); + let opt_suffix = matches.get_one::(options::SUFFIX).is_some(); let opt_multiple = matches.get_flag(options::MULTIPLE); - let opt_zero = matches.get_flag(options::ZERO); let multiple_paths = opt_suffix || opt_multiple; let name_args_count = matches .get_many::(options::NAME) @@ -105,7 +107,6 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { .collect() }; - let line_ending = if opt_zero { "\0" } else { "\n" }; for path in paths { print!("{}{}", basename(path, suffix), line_ending); } diff --git a/src/uu/dirname/src/dirname.rs b/src/uu/dirname/src/dirname.rs index cecd6aec8b9..2f0cab15b06 100644 --- a/src/uu/dirname/src/dirname.rs +++ b/src/uu/dirname/src/dirname.rs @@ -9,6 +9,7 @@ use clap::{crate_version, Arg, ArgAction, Command}; use std::path::Path; use uucore::display::print_verbatim; use uucore::error::{UResult, UUsageError}; +use uucore::line_ending::LineEnding; use uucore::{format_usage, help_about, help_section, help_usage}; const ABOUT: &str = help_about!("dirname.md"); @@ -26,11 +27,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let matches = uu_app().after_help(AFTER_HELP).try_get_matches_from(args)?; - let separator = if matches.get_flag(options::ZERO) { - "\0" - } else { - "\n" - }; + let line_ending = LineEnding::from(matches.get_flag(options::ZERO)); let dirnames: Vec = matches .get_many::(options::DIR) @@ -59,7 +56,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } } } - print!("{separator}"); + print!("{line_ending}"); } } diff --git a/src/uu/du/src/du.rs b/src/uu/du/src/du.rs index db385c720ea..9dc23d92bc5 100644 --- a/src/uu/du/src/du.rs +++ b/src/uu/du/src/du.rs @@ -34,6 +34,7 @@ use std::{error::Error, fmt::Display}; use uucore::display::{print_verbatim, Quotable}; use uucore::error::FromIo; use uucore::error::{set_exit_code, UError, UResult}; +use uucore::line_ending::LineEnding; use uucore::parse_glob; use uucore::parse_size::{parse_size, ParseSizeError}; use uucore::{ @@ -600,11 +601,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let time_format_str = parse_time_style(matches.get_one::("time-style").map(|s| s.as_str()))?; - let line_separator = if matches.get_flag(options::NULL) { - "\0" - } else { - "\n" - }; + let line_ending = LineEnding::from(matches.get_flag(options::NULL)); let excludes = build_exclude_patterns(&matches)?; @@ -656,12 +653,12 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let time_str = tm.format(time_format_str).to_string(); print!("{}\t{}\t", convert_size(size), time_str); print_verbatim(stat.path).unwrap(); - print!("{line_separator}"); + print!("{line_ending}"); } } else if !summarize || index == len - 1 { print!("{}\t", convert_size(size)); print_verbatim(stat.path).unwrap(); - print!("{line_separator}"); + print!("{line_ending}"); } if options.total && index == (len - 1) { // The last element will be the total size of the the path under @@ -681,7 +678,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { if options.total { print!("{}\ttotal", convert_size(grand_total)); - print!("{line_separator}"); + print!("{line_ending}"); } Ok(()) From ef38208061b0c2ae3178f1c66822f48ca8e33340 Mon Sep 17 00:00:00 2001 From: Simon Legner Date: Sun, 30 Jul 2023 11:09:50 +0200 Subject: [PATCH 05/12] Remove uucore::line_ending::LineEnding::Space --- src/uucore/src/lib/mods/line_ending.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/uucore/src/lib/mods/line_ending.rs b/src/uucore/src/lib/mods/line_ending.rs index b2e7009f73e..e595d43cd0c 100644 --- a/src/uucore/src/lib/mods/line_ending.rs +++ b/src/uucore/src/lib/mods/line_ending.rs @@ -8,7 +8,6 @@ pub enum LineEnding { #[default] Newline = b'\n', Nul = 0, - Space = b' ', None = 8, // abuse backspace \b to encode None } @@ -17,7 +16,6 @@ impl Display for LineEnding { match self { Self::Newline => writeln!(f), Self::Nul => write!(f, "\0"), - Self::Space => write!(f, " "), Self::None => std::fmt::Result::Ok(()), } } From 621f6a366a40e7762fe74682517b9f25d6fd6705 Mon Sep 17 00:00:00 2001 From: Simon Legner Date: Sun, 30 Jul 2023 11:46:29 +0200 Subject: [PATCH 06/12] Rename LineEnding::from_zero_flag --- src/uu/basename/src/basename.rs | 2 +- src/uu/comm/src/comm.rs | 4 ++-- src/uu/cut/src/cut.rs | 6 +++--- src/uu/dirname/src/dirname.rs | 2 +- src/uu/du/src/du.rs | 2 +- src/uu/env/src/env.rs | 2 +- src/uu/head/src/head.rs | 2 +- src/uu/id/src/id.rs | 2 +- src/uu/join/src/join.rs | 2 +- src/uu/ls/src/ls.rs | 2 +- src/uu/paste/src/paste.rs | 2 +- src/uu/readlink/src/readlink.rs | 2 +- src/uu/realpath/src/realpath.rs | 2 +- src/uu/sort/src/sort.rs | 2 +- src/uucore/src/lib/mods/line_ending.rs | 4 ++-- 15 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/uu/basename/src/basename.rs b/src/uu/basename/src/basename.rs index ad9487a4e6f..3fe594c3b3e 100644 --- a/src/uu/basename/src/basename.rs +++ b/src/uu/basename/src/basename.rs @@ -55,7 +55,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { return Err(UUsageError::new(1, "missing operand".to_string())); } - let line_ending = LineEnding::from(matches.get_flag(options::ZERO)); + let line_ending = LineEnding::from_zero_flag(matches.get_flag(options::ZERO)); let opt_suffix = matches.get_one::(options::SUFFIX).is_some(); let opt_multiple = matches.get_flag(options::MULTIPLE); diff --git a/src/uu/comm/src/comm.rs b/src/uu/comm/src/comm.rs index 088a921eb5e..0adbc84ec35 100644 --- a/src/uu/comm/src/comm.rs +++ b/src/uu/comm/src/comm.rs @@ -137,7 +137,7 @@ fn comm(a: &mut LineReader, b: &mut LineReader, opts: &ArgMatches) { } if opts.get_flag(options::TOTAL) { - let line_ending = LineEnding::from(opts.get_flag(options::ZERO_TERMINATED)); + let line_ending = LineEnding::from_zero_flag(opts.get_flag(options::ZERO_TERMINATED)); print!("{total_col_1}{delim}{total_col_2}{delim}{total_col_3}{delim}total{line_ending}"); } } @@ -159,7 +159,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let args = args.collect_lossy(); let matches = uu_app().try_get_matches_from(args)?; - let line_ending = LineEnding::from(matches.get_flag(options::ZERO_TERMINATED)); + let line_ending = LineEnding::from_zero_flag(matches.get_flag(options::ZERO_TERMINATED)); let filename1 = matches.get_one::(options::FILE_1).unwrap(); let filename2 = matches.get_one::(options::FILE_2).unwrap(); let mut f1 = open_file(filename1, line_ending).map_err_context(|| filename1.to_string())?; diff --git a/src/uu/cut/src/cut.rs b/src/uu/cut/src/cut.rs index aec2df8073f..33dd3850de9 100644 --- a/src/uu/cut/src/cut.rs +++ b/src/uu/cut/src/cut.rs @@ -377,7 +377,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { .unwrap_or_default() .to_owned(), ), - line_ending: LineEnding::from(matches.get_flag(options::ZERO_TERMINATED)), + line_ending: LineEnding::from_zero_flag(matches.get_flag(options::ZERO_TERMINATED)), }, ) }), @@ -392,7 +392,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { .unwrap_or_default() .to_owned(), ), - line_ending: LineEnding::from(matches.get_flag(options::ZERO_TERMINATED)), + line_ending: LineEnding::from_zero_flag(matches.get_flag(options::ZERO_TERMINATED)), }, ) }), @@ -412,7 +412,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let only_delimited = matches.get_flag(options::ONLY_DELIMITED); let whitespace_delimited = matches.get_flag(options::WHITESPACE_DELIMITED); let zero_terminated = matches.get_flag(options::ZERO_TERMINATED); - let line_ending = LineEnding::from(zero_terminated); + let line_ending = LineEnding::from_zero_flag(zero_terminated); match matches.get_one::(options::DELIMITER).map(|s| s.as_str()) { Some(_) if whitespace_delimited => { diff --git a/src/uu/dirname/src/dirname.rs b/src/uu/dirname/src/dirname.rs index 2f0cab15b06..4d9c09cc32e 100644 --- a/src/uu/dirname/src/dirname.rs +++ b/src/uu/dirname/src/dirname.rs @@ -27,7 +27,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let matches = uu_app().after_help(AFTER_HELP).try_get_matches_from(args)?; - let line_ending = LineEnding::from(matches.get_flag(options::ZERO)); + let line_ending = LineEnding::from_zero_flag(matches.get_flag(options::ZERO)); let dirnames: Vec = matches .get_many::(options::DIR) diff --git a/src/uu/du/src/du.rs b/src/uu/du/src/du.rs index 9dc23d92bc5..14bda496772 100644 --- a/src/uu/du/src/du.rs +++ b/src/uu/du/src/du.rs @@ -601,7 +601,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let time_format_str = parse_time_style(matches.get_one::("time-style").map(|s| s.as_str()))?; - let line_ending = LineEnding::from(matches.get_flag(options::NULL)); + let line_ending = LineEnding::from_zero_flag(matches.get_flag(options::NULL)); let excludes = build_exclude_patterns(&matches)?; diff --git a/src/uu/env/src/env.rs b/src/uu/env/src/env.rs index f00cf695a8a..08704f364ee 100644 --- a/src/uu/env/src/env.rs +++ b/src/uu/env/src/env.rs @@ -182,7 +182,7 @@ fn run_env(args: impl uucore::Args) -> UResult<()> { let matches = app.try_get_matches_from(args).with_exit_code(125)?; let ignore_env = matches.get_flag("ignore-environment"); - let line_ending = LineEnding::from(matches.get_flag("null")); + let line_ending = LineEnding::from_zero_flag(matches.get_flag("null")); let running_directory = matches.get_one::("chdir").map(|s| s.as_str()); let files = match matches.get_many::("file") { Some(v) => v.map(|s| s.as_str()).collect(), diff --git a/src/uu/head/src/head.rs b/src/uu/head/src/head.rs index 3f2271613d8..4e05dd5984e 100644 --- a/src/uu/head/src/head.rs +++ b/src/uu/head/src/head.rs @@ -198,7 +198,7 @@ impl HeadOptions { options.quiet = matches.get_flag(options::QUIET_NAME); options.verbose = matches.get_flag(options::VERBOSE_NAME); - options.line_ending = LineEnding::from(matches.get_flag(options::ZERO_NAME)); + options.line_ending = LineEnding::from_zero_flag(matches.get_flag(options::ZERO_NAME)); options.presume_input_pipe = matches.get_flag(options::PRESUME_INPUT_PIPE); options.mode = Mode::from(matches)?; diff --git a/src/uu/id/src/id.rs b/src/uu/id/src/id.rs index 52b0750e0f7..9f4849f9036 100644 --- a/src/uu/id/src/id.rs +++ b/src/uu/id/src/id.rs @@ -175,7 +175,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { " ".to_string() } }; - let line_ending = LineEnding::from(state.zflag); + let line_ending = LineEnding::from_zero_flag(state.zflag); if state.cflag { if state.selinux_supported { diff --git a/src/uu/join/src/join.rs b/src/uu/join/src/join.rs index 692a8277c77..afcb4d7d2e3 100644 --- a/src/uu/join/src/join.rs +++ b/src/uu/join/src/join.rs @@ -677,7 +677,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { settings.headers = true; } - settings.line_ending = LineEnding::from(matches.get_flag("z")); + settings.line_ending = LineEnding::from_zero_flag(matches.get_flag("z")); let file1 = matches.get_one::("file1").unwrap(); let file2 = matches.get_one::("file2").unwrap(); diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 98f434e010e..34d3b76f263 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -1006,7 +1006,7 @@ impl Config { } }, group_directories_first: options.get_flag(options::GROUP_DIRECTORIES_FIRST), - line_ending: LineEnding::from(options.get_flag(options::ZERO)), + line_ending: LineEnding::from_zero_flag(options.get_flag(options::ZERO)), }) } } diff --git a/src/uu/paste/src/paste.rs b/src/uu/paste/src/paste.rs index f5e95e821b5..45ba2d8dc40 100644 --- a/src/uu/paste/src/paste.rs +++ b/src/uu/paste/src/paste.rs @@ -48,7 +48,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { .unwrap() .map(|s| s.to_owned()) .collect(); - let line_ending = LineEnding::from(matches.get_flag(options::ZERO_TERMINATED)); + let line_ending = LineEnding::from_zero_flag(matches.get_flag(options::ZERO_TERMINATED)); paste(files, serial, delimiters, line_ending) } diff --git a/src/uu/readlink/src/readlink.rs b/src/uu/readlink/src/readlink.rs index 77de085b389..d9a1f4383b3 100644 --- a/src/uu/readlink/src/readlink.rs +++ b/src/uu/readlink/src/readlink.rs @@ -71,7 +71,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let line_ending = if no_trailing_delimiter { LineEnding::None } else { - LineEnding::from(use_zero) + LineEnding::from_zero_flag(use_zero) }; for f in &files { diff --git a/src/uu/realpath/src/realpath.rs b/src/uu/realpath/src/realpath.rs index 6afd12787ff..e2b7d7557c8 100644 --- a/src/uu/realpath/src/realpath.rs +++ b/src/uu/realpath/src/realpath.rs @@ -53,7 +53,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { .collect(); let strip = matches.get_flag(OPT_STRIP); - let line_ending = LineEnding::from(matches.get_flag(OPT_ZERO)); + let line_ending = LineEnding::from_zero_flag(matches.get_flag(OPT_ZERO)); let quiet = matches.get_flag(OPT_QUIET); let logical = matches.get_flag(OPT_LOGICAL); let can_mode = if matches.get_flag(OPT_CANONICALIZE_EXISTING) { diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index bb270c6f41f..aeec56d031b 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -1167,7 +1167,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { })?; } - settings.line_ending = LineEnding::from(matches.get_flag(options::ZERO_TERMINATED)); + settings.line_ending = LineEnding::from_zero_flag(matches.get_flag(options::ZERO_TERMINATED)); settings.merge = matches.get_flag(options::MERGE); settings.check = matches.contains_id(options::check::CHECK); diff --git a/src/uucore/src/lib/mods/line_ending.rs b/src/uucore/src/lib/mods/line_ending.rs index e595d43cd0c..314b5884e0a 100644 --- a/src/uucore/src/lib/mods/line_ending.rs +++ b/src/uucore/src/lib/mods/line_ending.rs @@ -30,8 +30,8 @@ impl From for u8 { } } -impl From for LineEnding { - fn from(is_zero_terminated: bool) -> Self { +impl LineEnding { + pub fn from_zero_flag(is_zero_terminated: bool) -> Self { if is_zero_terminated { Self::Nul } else { From 9675409277b0cbed5ae71918615eb1a527122067 Mon Sep 17 00:00:00 2001 From: Simon Legner Date: Sun, 30 Jul 2023 11:52:36 +0200 Subject: [PATCH 07/12] Replace LineEnding::None with Option --- src/uu/readlink/src/readlink.rs | 11 +++++++---- src/uucore/src/lib/mods/line_ending.rs | 7 +------ 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/uu/readlink/src/readlink.rs b/src/uu/readlink/src/readlink.rs index d9a1f4383b3..034f208fbf9 100644 --- a/src/uu/readlink/src/readlink.rs +++ b/src/uu/readlink/src/readlink.rs @@ -69,9 +69,9 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { no_trailing_delimiter = false; } let line_ending = if no_trailing_delimiter { - LineEnding::None + None } else { - LineEnding::from_zero_flag(use_zero) + Some(LineEnding::from_zero_flag(use_zero)) }; for f in &files { @@ -179,8 +179,11 @@ pub fn uu_app() -> Command { ) } -fn show(path: &Path, line_ending: LineEnding) -> std::io::Result<()> { +fn show(path: &Path, line_ending: Option) -> std::io::Result<()> { let path = path.to_str().unwrap(); - print!("{path}{line_ending}"); + print!("{path}"); + if let Some(line_ending) = line_ending { + print!("{line_ending}") + } stdout().flush() } diff --git a/src/uucore/src/lib/mods/line_ending.rs b/src/uucore/src/lib/mods/line_ending.rs index 314b5884e0a..8a068e9689a 100644 --- a/src/uucore/src/lib/mods/line_ending.rs +++ b/src/uucore/src/lib/mods/line_ending.rs @@ -8,7 +8,6 @@ pub enum LineEnding { #[default] Newline = b'\n', Nul = 0, - None = 8, // abuse backspace \b to encode None } impl Display for LineEnding { @@ -16,17 +15,13 @@ impl Display for LineEnding { match self { Self::Newline => writeln!(f), Self::Nul => write!(f, "\0"), - Self::None => std::fmt::Result::Ok(()), } } } impl From for u8 { fn from(line_ending: LineEnding) -> Self { - match line_ending { - LineEnding::None => panic!("Cannot convert LineEnding::None to u8"), - _ => line_ending as Self, - } + line_ending as Self } } From 8c314e156e53bb17f2c9d6e03929e0cf0ebb9cdb Mon Sep 17 00:00:00 2001 From: Simon Legner Date: Sun, 30 Jul 2023 15:00:35 +0200 Subject: [PATCH 08/12] cargo clippy --- src/uu/sort/src/sort.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index aeec56d031b..b40eb05c171 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -527,11 +527,11 @@ impl<'a> Line<'a> { } fn print(&self, writer: &mut impl Write, settings: &GlobalSettings) { - if !settings.debug { - writer.write_all(self.line.as_bytes()).unwrap(); - writer.write(&[settings.line_ending.into()]).unwrap(); - } else { + if settings.debug { self.print_debug(settings, writer).unwrap(); + } else { + writer.write_all(self.line.as_bytes()).unwrap(); + writer.write_all(&[settings.line_ending.into()]).unwrap(); } } From f137e92483b9994962d8b3e105262cde313d4fe1 Mon Sep 17 00:00:00 2001 From: Simon Legner Date: Sun, 30 Jul 2023 15:35:15 +0200 Subject: [PATCH 09/12] assert_eq --- src/uu/head/src/head.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/head/src/head.rs b/src/uu/head/src/head.rs index 4e05dd5984e..931f8a65242 100644 --- a/src/uu/head/src/head.rs +++ b/src/uu/head/src/head.rs @@ -570,7 +570,7 @@ mod tests { assert!(!opts.verbose); assert!(!opts.quiet); - assert!(opts.line_ending == LineEnding::Newline); + assert_eq!(opts.line_ending, LineEnding::Newline); assert_eq!(opts.mode, Mode::FirstLines(10)); assert!(opts.files.is_empty()); } From e536984f54dde83af24cd785188e5a5d57694cd3 Mon Sep 17 00:00:00 2001 From: Simon Legner Date: Sun, 30 Jul 2023 16:36:33 +0200 Subject: [PATCH 10/12] cargo clippy --- src/uu/comm/src/comm.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/uu/comm/src/comm.rs b/src/uu/comm/src/comm.rs index 0adbc84ec35..b88087389e2 100644 --- a/src/uu/comm/src/comm.rs +++ b/src/uu/comm/src/comm.rs @@ -8,7 +8,6 @@ // spell-checker:ignore (ToDO) delim mkdelim use std::cmp::Ordering; -use std::fmt::Display; use std::fs::File; use std::io::{self, stdin, BufRead, BufReader, Stdin}; use std::path::Path; From 93c4f81f51c278985bf8c8d43ae19b5d126d00af Mon Sep 17 00:00:00 2001 From: Simon Legner Date: Fri, 18 Aug 2023 20:35:32 +0200 Subject: [PATCH 11/12] cargo clippy --- src/uu/readlink/src/readlink.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/readlink/src/readlink.rs b/src/uu/readlink/src/readlink.rs index 034f208fbf9..7e9f7be15e6 100644 --- a/src/uu/readlink/src/readlink.rs +++ b/src/uu/readlink/src/readlink.rs @@ -183,7 +183,7 @@ fn show(path: &Path, line_ending: Option) -> std::io::Result<()> { let path = path.to_str().unwrap(); print!("{path}"); if let Some(line_ending) = line_ending { - print!("{line_ending}") + print!("{line_ending}"); } stdout().flush() } From cfb619601d5688db4db858531e6c04a6d677d1c4 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Fri, 18 Aug 2023 23:06:06 +0200 Subject: [PATCH 12/12] uucore/line_ending: add more documentation --- src/uucore/src/lib/mods/line_ending.rs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/uucore/src/lib/mods/line_ending.rs b/src/uucore/src/lib/mods/line_ending.rs index 8a068e9689a..073743fc575 100644 --- a/src/uucore/src/lib/mods/line_ending.rs +++ b/src/uucore/src/lib/mods/line_ending.rs @@ -1,9 +1,18 @@ -//! Provides consistent newline/zero terminator handling +//! Provides consistent newline/zero terminator handling for `-z`/`--zero` flags. +//! +//! See the [`LineEnding`] struct for more information. use std::fmt::Display; +/// Line ending of either `\n` or `\0` +/// +/// Used by various utilities that have the option to separate lines by nul +/// characters instead of `\n`. Usually, this is specified with the `-z` or +/// `--zero` flag. +/// +/// The [`Display`] implementation writes the character corresponding to the +/// variant to the formatter. #[repr(u8)] #[derive(Clone, Copy, Debug, Default, PartialEq)] -/// Line terminator used for printing and parsing pub enum LineEnding { #[default] Newline = b'\n', @@ -26,6 +35,10 @@ impl From for u8 { } impl LineEnding { + /// Create a [`LineEnding`] from a `-z`/`--zero` flag + /// + /// If `is_zero_terminated` is true, [`LineEnding::Nul`] is returned, + /// otherwise [`LineEnding::Newline`]. pub fn from_zero_flag(is_zero_terminated: bool) -> Self { if is_zero_terminated { Self::Nul