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

Extract uucore::line_ending::LineEnding (#5120)

* Extract uucore::line_ending::LineEnding

Aims to provide consistent newline/zero terminator handling.

* Apply suggestions from code review

Co-authored-by: Terts Diepraam <terts.diepraam@gmail.com>

* cargo fmt

* Use uucore::line_ending::LineEnding

* Remove uucore::line_ending::LineEnding::Space

* Rename LineEnding::from_zero_flag

* Replace LineEnding::None with Option<LineEnding>

* cargo clippy

* assert_eq

* cargo clippy

* cargo clippy

* uucore/line_ending: add more documentation

---------

Co-authored-by: Terts Diepraam <terts.diepraam@gmail.com>
This commit is contained in:
Simon Legner 2023-08-20 10:03:29 +02:00 committed by GitHub
parent 3b0bfb10ac
commit 872818607f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 163 additions and 196 deletions

View file

@ -11,6 +11,7 @@ use clap::{crate_version, Arg, ArgAction, Command};
use std::path::{is_separator, PathBuf}; use std::path::{is_separator, PathBuf};
use uucore::display::Quotable; use uucore::display::Quotable;
use uucore::error::{UResult, UUsageError}; use uucore::error::{UResult, UUsageError};
use uucore::line_ending::LineEnding;
use uucore::{format_usage, help_about, help_usage}; use uucore::{format_usage, help_about, help_usage};
static ABOUT: &str = help_about!("basename.md"); 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())); return Err(UUsageError::new(1, "missing operand".to_string()));
} }
let line_ending = LineEnding::from_zero_flag(matches.get_flag(options::ZERO));
let opt_suffix = matches.get_one::<String>(options::SUFFIX).is_some(); let opt_suffix = matches.get_one::<String>(options::SUFFIX).is_some();
let opt_multiple = matches.get_flag(options::MULTIPLE); let opt_multiple = matches.get_flag(options::MULTIPLE);
let opt_zero = matches.get_flag(options::ZERO);
let multiple_paths = opt_suffix || opt_multiple; let multiple_paths = opt_suffix || opt_multiple;
let name_args_count = matches let name_args_count = matches
.get_many::<String>(options::NAME) .get_many::<String>(options::NAME)
@ -105,7 +107,6 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
.collect() .collect()
}; };
let line_ending = if opt_zero { "\0" } else { "\n" };
for path in paths { for path in paths {
print!("{}{}", basename(path, suffix), line_ending); print!("{}{}", basename(path, suffix), line_ending);
} }

View file

@ -8,11 +8,11 @@
// spell-checker:ignore (ToDO) delim mkdelim // spell-checker:ignore (ToDO) delim mkdelim
use std::cmp::Ordering; use std::cmp::Ordering;
use std::fmt::Display;
use std::fs::File; use std::fs::File;
use std::io::{self, stdin, BufRead, BufReader, Stdin}; use std::io::{self, stdin, BufRead, BufReader, Stdin};
use std::path::Path; use std::path::Path;
use uucore::error::{FromIo, UResult}; use uucore::error::{FromIo, UResult};
use uucore::line_ending::LineEnding;
use uucore::{format_usage, help_about, help_usage}; use uucore::{format_usage, help_about, help_usage};
use clap::{crate_version, Arg, ArgAction, ArgMatches, Command}; use clap::{crate_version, Arg, ArgAction, ArgMatches, Command};
@ -40,38 +40,6 @@ fn column_width(col: &str, opts: &ArgMatches) -> usize {
} }
} }
#[repr(u8)]
#[derive(Clone, Copy)]
enum LineEnding {
Newline = b'\n',
Nul = 0,
}
impl From<LineEnding> for u8 {
fn from(line_ending: LineEnding) -> Self {
line_ending as Self
}
}
impl From<bool> 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 { enum Input {
Stdin(Stdin), Stdin(Stdin),
FileIn(BufReader<File>), FileIn(BufReader<File>),
@ -168,7 +136,7 @@ fn comm(a: &mut LineReader, b: &mut LineReader, opts: &ArgMatches) {
} }
if opts.get_flag(options::TOTAL) { 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}"); print!("{total_col_1}{delim}{total_col_2}{delim}{total_col_3}{delim}total{line_ending}");
} }
} }
@ -190,7 +158,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let args = args.collect_lossy(); let args = args.collect_lossy();
let matches = uu_app().try_get_matches_from(args)?; 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::<String>(options::FILE_1).unwrap(); let filename1 = matches.get_one::<String>(options::FILE_1).unwrap();
let filename2 = matches.get_one::<String>(options::FILE_2).unwrap(); let filename2 = matches.get_one::<String>(options::FILE_2).unwrap();
let mut f1 = open_file(filename1, line_ending).map_err_context(|| filename1.to_string())?; let mut f1 = open_file(filename1, line_ending).map_err_context(|| filename1.to_string())?;

View file

@ -15,6 +15,7 @@ use std::io::{stdin, stdout, BufReader, BufWriter, Read, Write};
use std::path::Path; use std::path::Path;
use uucore::display::Quotable; use uucore::display::Quotable;
use uucore::error::{FromIo, UResult, USimpleError}; use uucore::error::{FromIo, UResult, USimpleError};
use uucore::line_ending::LineEnding;
use self::searcher::Searcher; use self::searcher::Searcher;
use matcher::{ExactMatcher, Matcher, WhitespaceMatcher}; use matcher::{ExactMatcher, Matcher, WhitespaceMatcher};
@ -30,7 +31,7 @@ const AFTER_HELP: &str = help_section!("after help", "cut.md");
struct Options { struct Options {
out_delim: Option<String>, out_delim: Option<String>,
zero_terminated: bool, line_ending: LineEnding,
} }
enum Delimiter { enum Delimiter {
@ -42,7 +43,7 @@ struct FieldOptions {
delimiter: Delimiter, delimiter: Delimiter,
out_delimiter: Option<String>, out_delimiter: Option<String>,
only_delimited: bool, only_delimited: bool,
zero_terminated: bool, line_ending: LineEnding,
} }
enum Mode { enum Mode {
@ -68,7 +69,7 @@ fn list_to_ranges(list: &str, complement: bool) -> Result<Vec<Range>, String> {
} }
fn cut_bytes<R: Read>(reader: R, ranges: &[Range], opts: &Options) -> UResult<()> { fn cut_bytes<R: Read>(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 buf_in = BufReader::new(reader);
let mut out = stdout_writer(); let mut out = stdout_writer();
let delim = opts let delim = opts
@ -259,7 +260,7 @@ fn cut_fields_implicit_out_delim<R: Read, M: Matcher>(
} }
fn cut_fields<R: Read>(reader: R, ranges: &[Range], opts: &FieldOptions) -> UResult<()> { fn cut_fields<R: Read>(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 { match opts.delimiter {
Delimiter::String(ref delim) => { Delimiter::String(ref delim) => {
let matcher = ExactMatcher::new(delim.as_bytes()); let matcher = ExactMatcher::new(delim.as_bytes());
@ -376,7 +377,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
.unwrap_or_default() .unwrap_or_default()
.to_owned(), .to_owned(),
), ),
zero_terminated: matches.get_flag(options::ZERO_TERMINATED), line_ending: LineEnding::from_zero_flag(matches.get_flag(options::ZERO_TERMINATED)),
}, },
) )
}), }),
@ -391,7 +392,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
.unwrap_or_default() .unwrap_or_default()
.to_owned(), .to_owned(),
), ),
zero_terminated: matches.get_flag(options::ZERO_TERMINATED), line_ending: LineEnding::from_zero_flag(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 only_delimited = matches.get_flag(options::ONLY_DELIMITED);
let whitespace_delimited = matches.get_flag(options::WHITESPACE_DELIMITED); let whitespace_delimited = matches.get_flag(options::WHITESPACE_DELIMITED);
let zero_terminated = matches.get_flag(options::ZERO_TERMINATED); let zero_terminated = matches.get_flag(options::ZERO_TERMINATED);
let line_ending = LineEnding::from_zero_flag(zero_terminated);
match matches.get_one::<String>(options::DELIMITER).map(|s| s.as_str()) { match matches.get_one::<String>(options::DELIMITER).map(|s| s.as_str()) {
Some(_) if whitespace_delimited => { Some(_) if whitespace_delimited => {
@ -441,7 +443,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
delimiter: Delimiter::String(delim), delimiter: Delimiter::String(delim),
out_delimiter: out_delim, out_delimiter: out_delim,
only_delimited, only_delimited,
zero_terminated, line_ending,
}, },
)) ))
} }
@ -455,7 +457,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
}, },
out_delimiter: out_delim, out_delimiter: out_delim,
only_delimited, only_delimited,
zero_terminated, line_ending,
}, },
)), )),
} }

View file

@ -9,6 +9,7 @@ use clap::{crate_version, Arg, ArgAction, Command};
use std::path::Path; use std::path::Path;
use uucore::display::print_verbatim; use uucore::display::print_verbatim;
use uucore::error::{UResult, UUsageError}; use uucore::error::{UResult, UUsageError};
use uucore::line_ending::LineEnding;
use uucore::{format_usage, help_about, help_section, help_usage}; use uucore::{format_usage, help_about, help_section, help_usage};
const ABOUT: &str = help_about!("dirname.md"); 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 matches = uu_app().after_help(AFTER_HELP).try_get_matches_from(args)?;
let separator = if matches.get_flag(options::ZERO) { let line_ending = LineEnding::from_zero_flag(matches.get_flag(options::ZERO));
"\0"
} else {
"\n"
};
let dirnames: Vec<String> = matches let dirnames: Vec<String> = matches
.get_many::<String>(options::DIR) .get_many::<String>(options::DIR)
@ -59,7 +56,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
} }
} }
} }
print!("{separator}"); print!("{line_ending}");
} }
} }

View file

@ -34,6 +34,7 @@ use std::{error::Error, fmt::Display};
use uucore::display::{print_verbatim, Quotable}; use uucore::display::{print_verbatim, Quotable};
use uucore::error::FromIo; use uucore::error::FromIo;
use uucore::error::{set_exit_code, UError, UResult}; use uucore::error::{set_exit_code, UError, UResult};
use uucore::line_ending::LineEnding;
use uucore::parse_glob; use uucore::parse_glob;
use uucore::parse_size::{parse_size, ParseSizeError}; use uucore::parse_size::{parse_size, ParseSizeError};
use uucore::{ use uucore::{
@ -600,11 +601,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let time_format_str = let time_format_str =
parse_time_style(matches.get_one::<String>("time-style").map(|s| s.as_str()))?; parse_time_style(matches.get_one::<String>("time-style").map(|s| s.as_str()))?;
let line_separator = if matches.get_flag(options::NULL) { let line_ending = LineEnding::from_zero_flag(matches.get_flag(options::NULL));
"\0"
} else {
"\n"
};
let excludes = build_exclude_patterns(&matches)?; 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(); let time_str = tm.format(time_format_str).to_string();
print!("{}\t{}\t", convert_size(size), time_str); print!("{}\t{}\t", convert_size(size), time_str);
print_verbatim(stat.path).unwrap(); print_verbatim(stat.path).unwrap();
print!("{line_separator}"); print!("{line_ending}");
} }
} else if !summarize || index == len - 1 { } else if !summarize || index == len - 1 {
print!("{}\t", convert_size(size)); print!("{}\t", convert_size(size));
print_verbatim(stat.path).unwrap(); print_verbatim(stat.path).unwrap();
print!("{line_separator}"); print!("{line_ending}");
} }
if options.total && index == (len - 1) { if options.total && index == (len - 1) {
// The last element will be the total size of the the path under // 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 { if options.total {
print!("{}\ttotal", convert_size(grand_total)); print!("{}\ttotal", convert_size(grand_total));
print!("{line_separator}"); print!("{line_ending}");
} }
Ok(()) Ok(())

15
src/uu/env/src/env.rs vendored
View file

@ -23,6 +23,7 @@ use std::os::unix::process::ExitStatusExt;
use std::process; use std::process;
use uucore::display::Quotable; use uucore::display::Quotable;
use uucore::error::{UClapError, UResult, USimpleError, UUsageError}; use uucore::error::{UClapError, UResult, USimpleError, UUsageError};
use uucore::line_ending::LineEnding;
use uucore::{format_usage, help_about, help_section, help_usage, show_warning}; use uucore::{format_usage, help_about, help_section, help_usage, show_warning};
const ABOUT: &str = help_about!("env.md"); const ABOUT: &str = help_about!("env.md");
@ -31,7 +32,7 @@ const AFTER_HELP: &str = help_section!("after help", "env.md");
struct Options<'a> { struct Options<'a> {
ignore_env: bool, ignore_env: bool,
null: bool, line_ending: LineEnding,
running_directory: Option<&'a str>, running_directory: Option<&'a str>,
files: Vec<&'a str>, files: Vec<&'a str>,
unsets: Vec<&'a str>, unsets: Vec<&'a str>,
@ -41,11 +42,11 @@ struct Options<'a> {
// print name=value env pairs on screen // print name=value env pairs on screen
// if null is true, separate pairs with a \0, \n otherwise // 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 stdout_raw = io::stdout();
let mut stdout = stdout_raw.lock(); let mut stdout = stdout_raw.lock();
for (n, v) in env::vars() { 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<boo
} }
fn parse_program_opt<'a>(opts: &mut Options<'a>, opt: &'a str) -> UResult<()> { fn parse_program_opt<'a>(opts: &mut Options<'a>, opt: &'a str) -> UResult<()> {
if opts.null { if opts.line_ending == LineEnding::Nul {
Err(UUsageError::new( Err(UUsageError::new(
125, 125,
"cannot specify --null (-0) with command".to_string(), "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 matches = app.try_get_matches_from(args).with_exit_code(125)?;
let ignore_env = matches.get_flag("ignore-environment"); let ignore_env = matches.get_flag("ignore-environment");
let null = matches.get_flag("null"); let line_ending = LineEnding::from_zero_flag(matches.get_flag("null"));
let running_directory = matches.get_one::<String>("chdir").map(|s| s.as_str()); let running_directory = matches.get_one::<String>("chdir").map(|s| s.as_str());
let files = match matches.get_many::<String>("file") { let files = match matches.get_many::<String>("file") {
Some(v) => v.map(|s| s.as_str()).collect(), 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 { let mut opts = Options {
ignore_env, ignore_env,
null, line_ending,
running_directory, running_directory,
files, files,
unsets, unsets,
@ -302,7 +303,7 @@ fn run_env(args: impl uucore::Args) -> UResult<()> {
if opts.program.is_empty() { if opts.program.is_empty() {
// no program provided, so just dump all env vars to stdout // no program provided, so just dump all env vars to stdout
print_env(opts.null); print_env(opts.line_ending);
} else { } else {
// we need to execute a command // we need to execute a command
let (prog, args) = build_command(&mut opts.program); let (prog, args) = build_command(&mut opts.program);

View file

@ -10,6 +10,7 @@ use std::ffi::OsString;
use std::io::{self, BufWriter, ErrorKind, Read, Seek, SeekFrom, Write}; use std::io::{self, BufWriter, ErrorKind, Read, Seek, SeekFrom, Write};
use uucore::display::Quotable; use uucore::display::Quotable;
use uucore::error::{FromIo, UError, UResult, USimpleError}; use uucore::error::{FromIo, UError, UResult, USimpleError};
use uucore::line_ending::LineEnding;
use uucore::lines::lines; use uucore::lines::lines;
use uucore::{format_usage, help_about, help_usage, show}; use uucore::{format_usage, help_about, help_usage, show};
@ -184,7 +185,7 @@ fn arg_iterate<'a>(
struct HeadOptions { struct HeadOptions {
pub quiet: bool, pub quiet: bool,
pub verbose: bool, pub verbose: bool,
pub zeroed: bool, pub line_ending: LineEnding,
pub presume_input_pipe: bool, pub presume_input_pipe: bool,
pub mode: Mode, pub mode: Mode,
pub files: Vec<String>, pub files: Vec<String>,
@ -197,7 +198,7 @@ impl HeadOptions {
options.quiet = matches.get_flag(options::QUIET_NAME); options.quiet = matches.get_flag(options::QUIET_NAME);
options.verbose = matches.get_flag(options::VERBOSE_NAME); options.verbose = matches.get_flag(options::VERBOSE_NAME);
options.zeroed = 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.presume_input_pipe = matches.get_flag(options::PRESUME_INPUT_PIPE);
options.mode = Mode::from(matches)?; options.mode = Mode::from(matches)?;
@ -227,9 +228,8 @@ where
Ok(()) 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. // 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); let mut reader = take_lines(input, n, separator);
// Write those bytes to `stdout`. // 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( fn read_but_last_n_lines(
input: impl std::io::BufRead, input: impl std::io::BufRead,
n: usize, n: usize,
zero: bool, separator: u8,
) -> std::io::Result<()> { ) -> std::io::Result<()> {
if zero { let stdout = std::io::stdout();
let stdout = std::io::stdout(); let mut stdout = stdout.lock();
let mut stdout = stdout.lock(); for bytes in take_all_but(lines(input, separator), n) {
for bytes in take_all_but(lines(input, b'\0'), n) { stdout.write_all(&bytes?)?;
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?)?;
}
} }
Ok(()) 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, 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, 1000, false).unwrap(), 0);
/// ``` /// ```
fn find_nth_line_from_end<R>(input: &mut R, n: u64, zeroed: bool) -> std::io::Result<u64> fn find_nth_line_from_end<R>(input: &mut R, n: u64, separator: u8) -> std::io::Result<u64>
where where
R: Read + Seek, R: Read + Seek,
{ {
@ -370,14 +362,8 @@ where
))?; ))?;
input.read_exact(buffer)?; input.read_exact(buffer)?;
for byte in buffer.iter().rev() { for byte in buffer.iter().rev() {
match byte { if byte == &separator {
b'\n' if !zeroed => { lines += 1;
lines += 1;
}
0u8 if zeroed => {
lines += 1;
}
_ => {}
} }
// if it were just `n`, // if it were just `n`,
if lines == n + 1 { if lines == n + 1 {
@ -407,7 +393,7 @@ fn head_backwards_file(input: &mut std::fs::File, options: &HeadOptions) -> std:
} }
} }
Mode::AllButLastLines(n) => { 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( read_n_bytes(
&mut std::io::BufReader::with_capacity(BUF_SIZE, input), &mut std::io::BufReader::with_capacity(BUF_SIZE, input),
found, found,
@ -426,7 +412,7 @@ fn head_file(input: &mut std::fs::File, options: &HeadOptions) -> std::io::Resul
Mode::FirstLines(n) => read_n_lines( Mode::FirstLines(n) => read_n_lines(
&mut std::io::BufReader::with_capacity(BUF_SIZE, input), &mut std::io::BufReader::with_capacity(BUF_SIZE, input),
n, n,
options.zeroed, options.line_ending.into(),
), ),
Mode::AllButLastBytes(_) | Mode::AllButLastLines(_) => head_backwards_file(input, options), Mode::AllButLastBytes(_) | Mode::AllButLastLines(_) => head_backwards_file(input, options),
} }
@ -466,11 +452,13 @@ fn uu_head(options: &HeadOptions) -> UResult<()> {
Mode::AllButLastBytes(n) => { Mode::AllButLastBytes(n) => {
read_but_last_n_bytes(&mut stdin, n.try_into().unwrap()) 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 // unwrap is guaranteed to succeed because we checked the value of n above
Mode::AllButLastLines(n) => { Mode::AllButLastLines(n) => read_but_last_n_lines(
read_but_last_n_lines(&mut stdin, n.try_into().unwrap(), options.zeroed) &mut stdin,
} n.try_into().unwrap(),
options.line_ending.into(),
),
} }
} }
(name, false) => { (name, false) => {
@ -541,7 +529,7 @@ mod tests {
#[test] #[test]
fn test_args_modes() { fn test_args_modes() {
let args = options("-n -10M -vz").unwrap(); let args = options("-n -10M -vz").unwrap();
assert!(args.zeroed); assert_eq!(args.line_ending, LineEnding::Nul);
assert!(args.verbose); assert!(args.verbose);
assert_eq!(args.mode, Mode::AllButLastLines(10 * 1024 * 1024)); assert_eq!(args.mode, Mode::AllButLastLines(10 * 1024 * 1024));
} }
@ -561,8 +549,11 @@ mod tests {
assert!(options("-q").unwrap().quiet); assert!(options("-q").unwrap().quiet);
assert!(options("--verbose").unwrap().verbose); assert!(options("--verbose").unwrap().verbose);
assert!(options("-v").unwrap().verbose); assert!(options("-v").unwrap().verbose);
assert!(options("--zero-terminated").unwrap().zeroed); assert_eq!(
assert!(options("-z").unwrap().zeroed); 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("--lines 15").unwrap().mode, Mode::FirstLines(15));
assert_eq!(options("-n 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)); assert_eq!(options("--bytes 15").unwrap().mode, Mode::FirstBytes(15));
@ -579,7 +570,7 @@ mod tests {
assert!(!opts.verbose); assert!(!opts.verbose);
assert!(!opts.quiet); assert!(!opts.quiet);
assert!(!opts.zeroed); assert_eq!(opts.line_ending, LineEnding::Newline);
assert_eq!(opts.mode, Mode::FirstLines(10)); assert_eq!(opts.mode, Mode::FirstLines(10));
assert!(opts.files.is_empty()); assert!(opts.files.is_empty());
} }
@ -631,17 +622,17 @@ mod tests {
fn read_early_exit() { fn read_early_exit() {
let mut empty = std::io::BufReader::new(std::io::Cursor::new(Vec::new())); 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_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] #[test]
fn test_find_nth_line_from_end() { fn test_find_nth_line_from_end() {
let mut input = Cursor::new("x\ny\nz\n"); 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, 0, b'\n').unwrap(), 6);
assert_eq!(find_nth_line_from_end(&mut input, 1, false).unwrap(), 4); assert_eq!(find_nth_line_from_end(&mut input, 1, b'\n').unwrap(), 4);
assert_eq!(find_nth_line_from_end(&mut input, 2, false).unwrap(), 2); assert_eq!(find_nth_line_from_end(&mut input, 2, b'\n').unwrap(), 2);
assert_eq!(find_nth_line_from_end(&mut input, 3, false).unwrap(), 0); assert_eq!(find_nth_line_from_end(&mut input, 3, b'\n').unwrap(), 0);
assert_eq!(find_nth_line_from_end(&mut input, 4, false).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, false).unwrap(), 0); assert_eq!(find_nth_line_from_end(&mut input, 1000, b'\n').unwrap(), 0);
} }
} }

View file

@ -44,6 +44,7 @@ use uucore::error::UResult;
use uucore::error::{set_exit_code, USimpleError}; use uucore::error::{set_exit_code, USimpleError};
pub use uucore::libc; pub use uucore::libc;
use uucore::libc::{getlogin, uid_t}; use uucore::libc::{getlogin, uid_t};
use uucore::line_ending::LineEnding;
use uucore::process::{getegid, geteuid, getgid, getuid}; use uucore::process::{getegid, geteuid, getgid, getuid};
use uucore::{format_usage, help_about, help_section, help_usage, show_error}; 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() " ".to_string()
} }
}; };
let line_ending = { let line_ending = LineEnding::from_zero_flag(state.zflag);
if state.zflag {
'\0'
} else {
'\n'
}
};
if state.cflag { if state.cflag {
if state.selinux_supported { if state.selinux_supported {

View file

@ -22,6 +22,7 @@ use std::num::IntErrorKind;
use std::os::unix::ffi::OsStrExt; use std::os::unix::ffi::OsStrExt;
use uucore::display::Quotable; use uucore::display::Quotable;
use uucore::error::{set_exit_code, UError, UResult, USimpleError}; 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}; use uucore::{crash, crash_if_err, format_usage, help_about, help_usage};
const ABOUT: &str = help_about!("join.md"); const ABOUT: &str = help_about!("join.md");
@ -62,13 +63,6 @@ enum FileNum {
File2, File2,
} }
#[repr(u8)]
#[derive(Copy, Clone)]
enum LineEnding {
Nul = 0,
Newline = b'\n',
}
#[derive(Copy, Clone, PartialEq)] #[derive(Copy, Clone, PartialEq)]
enum Sep { enum Sep {
Char(u8), Char(u8),
@ -683,9 +677,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
settings.headers = true; settings.headers = true;
} }
if matches.get_flag("z") { settings.line_ending = LineEnding::from_zero_flag(matches.get_flag("z"));
settings.line_ending = LineEnding::Nul;
}
let file1 = matches.get_one::<String>("file1").unwrap(); let file1 = matches.get_one::<String>("file1").unwrap();
let file2 = matches.get_one::<String>("file2").unwrap(); let file2 = matches.get_one::<String>("file2").unwrap();

View file

@ -54,6 +54,7 @@ use unicode_width::UnicodeWidthStr;
use uucore::libc::{dev_t, major, minor}; use uucore::libc::{dev_t, major, minor};
#[cfg(unix)] #[cfg(unix)]
use uucore::libc::{S_IXGRP, S_IXOTH, S_IXUSR}; use uucore::libc::{S_IXGRP, S_IXOTH, S_IXUSR};
use uucore::line_ending::LineEnding;
use uucore::quoting_style::{escape_name, QuotingStyle}; use uucore::quoting_style::{escape_name, QuotingStyle};
use uucore::{ use uucore::{
display::Quotable, display::Quotable,
@ -408,7 +409,7 @@ pub struct Config {
context: bool, context: bool,
selinux_supported: bool, selinux_supported: bool,
group_directories_first: bool, group_directories_first: bool,
eol: char, line_ending: LineEnding,
} }
// Fields that can be removed or added to the long format // 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), group_directories_first: options.get_flag(options::GROUP_DIRECTORIES_FIRST),
eol: if options.get_flag(options::ZERO) { line_ending: LineEnding::from_zero_flag(options.get_flag(options::ZERO)),
'\0'
} else {
'\n'
},
}) })
} }
} }
@ -2173,7 +2170,7 @@ fn display_total(items: &[PathData], config: &Config, out: &mut BufWriter<Stdout
out, out,
"total {}{}", "total {}{}",
display_size(total_size, config), display_size(total_size, config),
config.eol config.line_ending
)?; )?;
Ok(()) Ok(())
} }
@ -2285,12 +2282,12 @@ fn display_items(items: &[PathData], config: &Config, out: &mut BufWriter<Stdout
// Current col is never zero again if names have been printed. // Current col is never zero again if names have been printed.
// So we print a newline. // So we print a newline.
if current_col > 0 { if current_col > 0 {
write!(out, "{}", config.eol)?; write!(out, "{}", config.line_ending)?;
} }
} }
_ => { _ => {
for name in names { for name in names {
write!(out, "{}{}", name.contents, config.eol)?; write!(out, "{}{}", name.contents, config.line_ending)?;
} }
} }
}; };
@ -2491,7 +2488,13 @@ fn display_item_long(
let dfn = display_file_name(item, config, None, String::new(), out).contents; 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 { } else {
#[cfg(unix)] #[cfg(unix)]
let leading_char = { let leading_char = {

View file

@ -8,11 +8,11 @@
// spell-checker:ignore (ToDO) delim // spell-checker:ignore (ToDO) delim
use clap::{crate_version, Arg, ArgAction, Command}; use clap::{crate_version, Arg, ArgAction, Command};
use std::fmt::Display;
use std::fs::File; use std::fs::File;
use std::io::{stdin, stdout, BufRead, BufReader, Read, Write}; use std::io::{stdin, stdout, BufRead, BufReader, Read, Write};
use std::path::Path; use std::path::Path;
use uucore::error::{FromIo, UResult, USimpleError}; use uucore::error::{FromIo, UResult, USimpleError};
use uucore::line_ending::LineEnding;
use uucore::{format_usage, help_about, help_usage}; use uucore::{format_usage, help_about, help_usage};
const ABOUT: &str = help_about!("paste.md"); const ABOUT: &str = help_about!("paste.md");
@ -25,22 +25,6 @@ mod options {
pub const ZERO_TERMINATED: &str = "zero-terminated"; 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 // Wraps BufReader and stdin
fn read_until<R: Read>( fn read_until<R: Read>(
reader: Option<&mut BufReader<R>>, reader: Option<&mut BufReader<R>>,
@ -64,11 +48,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
.unwrap() .unwrap()
.map(|s| s.to_owned()) .map(|s| s.to_owned())
.collect(); .collect();
let line_ending = if matches.get_flag(options::ZERO_TERMINATED) { let line_ending = LineEnding::from_zero_flag(matches.get_flag(options::ZERO_TERMINATED));
LineEnding::Nul
} else {
LineEnding::Newline
};
paste(files, serial, delimiters, line_ending) paste(files, serial, delimiters, line_ending)
} }

View file

@ -14,6 +14,7 @@ use std::path::{Path, PathBuf};
use uucore::display::Quotable; use uucore::display::Quotable;
use uucore::error::{FromIo, UResult, USimpleError, UUsageError}; use uucore::error::{FromIo, UResult, USimpleError, UUsageError};
use uucore::fs::{canonicalize, MissingHandling, ResolveMode}; use uucore::fs::{canonicalize, MissingHandling, ResolveMode};
use uucore::line_ending::LineEnding;
use uucore::{format_usage, help_about, help_usage, show_error}; use uucore::{format_usage, help_about, help_usage, show_error};
const ABOUT: &str = help_about!("readlink.md"); const ABOUT: &str = help_about!("readlink.md");
@ -67,6 +68,11 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
show_error!("ignoring --no-newline with multiple arguments"); show_error!("ignoring --no-newline with multiple arguments");
no_trailing_delimiter = false; no_trailing_delimiter = false;
} }
let line_ending = if no_trailing_delimiter {
None
} else {
Some(LineEnding::from_zero_flag(use_zero))
};
for f in &files { for f in &files {
let p = PathBuf::from(f); let p = PathBuf::from(f);
@ -77,7 +83,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
}; };
match path_result { match path_result {
Ok(path) => { 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) => { Err(err) => {
if verbose { if verbose {
@ -173,14 +179,11 @@ 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: Option<LineEnding>) -> std::io::Result<()> {
let path = path.to_str().unwrap(); let path = path.to_str().unwrap();
if no_trailing_delimiter { print!("{path}");
print!("{path}"); if let Some(line_ending) = line_ending {
} else if use_zero { print!("{line_ending}");
print!("{path}\0");
} else {
println!("{path}");
} }
stdout().flush() stdout().flush()
} }

View file

@ -21,6 +21,7 @@ use uucore::{
format_usage, format_usage,
fs::{canonicalize, MissingHandling, ResolveMode}, fs::{canonicalize, MissingHandling, ResolveMode},
help_about, help_usage, help_about, help_usage,
line_ending::LineEnding,
}; };
use uucore::{error::UClapError, show, show_if_err}; use uucore::{error::UClapError, show, show_if_err};
@ -52,7 +53,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
.collect(); .collect();
let strip = matches.get_flag(OPT_STRIP); let strip = matches.get_flag(OPT_STRIP);
let zero = 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 quiet = matches.get_flag(OPT_QUIET);
let logical = matches.get_flag(OPT_LOGICAL); let logical = matches.get_flag(OPT_LOGICAL);
let can_mode = if matches.get_flag(OPT_CANONICALIZE_EXISTING) { 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 { for path in &paths {
let result = resolve_path( let result = resolve_path(
path, path,
zero, line_ending,
resolve_mode, resolve_mode,
can_mode, can_mode,
relative_to.as_deref(), relative_to.as_deref(),
@ -249,19 +250,18 @@ fn canonicalize_relative(
/// symbolic links. /// symbolic links.
fn resolve_path( fn resolve_path(
p: &Path, p: &Path,
zero: bool, line_ending: LineEnding,
resolve: ResolveMode, resolve: ResolveMode,
can_mode: MissingHandling, can_mode: MissingHandling,
relative_to: Option<&Path>, relative_to: Option<&Path>,
relative_base: Option<&Path>, relative_base: Option<&Path>,
) -> std::io::Result<()> { ) -> std::io::Result<()> {
let abs = canonicalize(p, can_mode, resolve)?; 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); let abs = process_relative(abs, relative_base, relative_to);
print_verbatim(abs)?; print_verbatim(abs)?;
stdout().write_all(&[line_ending])?; stdout().write_all(&[line_ending.into()])?;
Ok(()) Ok(())
} }

View file

@ -115,11 +115,7 @@ fn reader(
&mut carry_over, &mut carry_over,
&mut file, &mut file,
&mut iter::empty(), &mut iter::empty(),
if settings.zero_terminated { settings.line_ending.into(),
b'\0'
} else {
b'\n'
},
settings, settings,
)?; )?;
if !should_continue { if !should_continue {

View file

@ -84,11 +84,7 @@ fn reader_writer<
output: Output, output: Output,
tmp_dir: &mut TmpDirWrapper, tmp_dir: &mut TmpDirWrapper,
) -> UResult<()> { ) -> UResult<()> {
let separator = if settings.zero_terminated { let separator = settings.line_ending.into();
b'\0'
} else {
b'\n'
};
// Heuristically chosen: Dividing by 10 seems to keep our memory usage roughly // Heuristically chosen: Dividing by 10 seems to keep our memory usage roughly
// around settings.buffer_size as a whole. // around settings.buffer_size as a whole.

View file

@ -169,11 +169,7 @@ fn merge_without_limit<M: MergeInput + 'static, F: Iterator<Item = UResult<M>>>(
&request_receiver, &request_receiver,
&mut reader_files, &mut reader_files,
&settings, &settings,
if settings.zero_terminated { settings.line_ending.into(),
b'\0'
} else {
b'\n'
},
) )
} }
}); });

View file

@ -45,6 +45,7 @@ use std::str::Utf8Error;
use unicode_width::UnicodeWidthStr; use unicode_width::UnicodeWidthStr;
use uucore::display::Quotable; use uucore::display::Quotable;
use uucore::error::{set_exit_code, strip_errno, UError, UResult, USimpleError, UUsageError}; 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::parse_size::{ParseSizeError, Parser};
use uucore::version_cmp::version_cmp; use uucore::version_cmp::version_cmp;
use uucore::{format_usage, help_about, help_section, help_usage}; use uucore::{format_usage, help_about, help_section, help_usage};
@ -306,7 +307,7 @@ pub struct GlobalSettings {
selectors: Vec<FieldSelector>, selectors: Vec<FieldSelector>,
separator: Option<char>, separator: Option<char>,
threads: String, threads: String,
zero_terminated: bool, line_ending: LineEnding,
buffer_size: usize, buffer_size: usize,
compress_prog: Option<String>, compress_prog: Option<String>,
merge_batch_size: usize, merge_batch_size: usize,
@ -383,7 +384,7 @@ impl Default for GlobalSettings {
selectors: vec![], selectors: vec![],
separator: None, separator: None,
threads: String::new(), threads: String::new(),
zero_terminated: false, line_ending: LineEnding::Newline,
buffer_size: DEFAULT_BUF_SIZE, buffer_size: DEFAULT_BUF_SIZE,
compress_prog: None, compress_prog: None,
merge_batch_size: 32, merge_batch_size: 32,
@ -526,14 +527,11 @@ impl<'a> Line<'a> {
} }
fn print(&self, writer: &mut impl Write, settings: &GlobalSettings) { 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();
} else {
self.print_debug(settings, writer).unwrap(); self.print_debug(settings, writer).unwrap();
} else {
writer.write_all(self.line.as_bytes()).unwrap();
writer.write_all(&[settings.line_ending.into()]).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_zero_flag(matches.get_flag(options::ZERO_TERMINATED));
settings.merge = matches.get_flag(options::MERGE); settings.merge = matches.get_flag(options::MERGE);
settings.check = matches.contains_id(options::check::CHECK); settings.check = matches.contains_id(options::check::CHECK);

View file

@ -22,6 +22,7 @@ pub use uucore_procs::*;
pub use crate::mods::backup_control; pub use crate::mods::backup_control;
pub use crate::mods::display; pub use crate::mods::display;
pub use crate::mods::error; pub use crate::mods::error;
pub use crate::mods::line_ending;
pub use crate::mods::os; pub use crate::mods::os;
pub use crate::mods::panic; pub use crate::mods::panic;
pub use crate::mods::quoting_style; pub use crate::mods::quoting_style;

View file

@ -3,6 +3,7 @@
pub mod backup_control; pub mod backup_control;
pub mod display; pub mod display;
pub mod error; pub mod error;
pub mod line_ending;
pub mod os; pub mod os;
pub mod panic; pub mod panic;
pub mod ranges; pub mod ranges;

View file

@ -0,0 +1,49 @@
//! 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)]
pub enum LineEnding {
#[default]
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"),
}
}
}
impl From<LineEnding> for u8 {
fn from(line_ending: LineEnding) -> Self {
line_ending as Self
}
}
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
} else {
Self::Newline
}
}
}