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 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_zero_flag(matches.get_flag(options::ZERO));
let opt_suffix = matches.get_one::<String>(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::<String>(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);
}

View file

@ -8,11 +8,11 @@
// 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;
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 +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 {
Stdin(Stdin),
FileIn(BufReader<File>),
@ -168,7 +136,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}");
}
}
@ -190,7 +158,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::<String>(options::FILE_1).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())?;

View file

@ -15,6 +15,7 @@ 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};
@ -30,7 +31,7 @@ const AFTER_HELP: &str = help_section!("after help", "cut.md");
struct Options {
out_delim: Option<String>,
zero_terminated: bool,
line_ending: LineEnding,
}
enum Delimiter {
@ -42,7 +43,7 @@ struct FieldOptions {
delimiter: Delimiter,
out_delimiter: Option<String>,
only_delimited: bool,
zero_terminated: bool,
line_ending: LineEnding,
}
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<()> {
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<R: Read, M: Matcher>(
}
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 {
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_zero_flag(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_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 whitespace_delimited = matches.get_flag(options::WHITESPACE_DELIMITED);
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()) {
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,
},
)),
}

View file

@ -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_zero_flag(matches.get_flag(options::ZERO));
let dirnames: Vec<String> = matches
.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::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::<String>("time-style").map(|s| s.as_str()))?;
let line_separator = if matches.get_flag(options::NULL) {
"\0"
} else {
"\n"
};
let line_ending = LineEnding::from_zero_flag(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(())

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

@ -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<boo
}
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(
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_zero_flag(matches.get_flag("null"));
let running_directory = matches.get_one::<String>("chdir").map(|s| s.as_str());
let files = match matches.get_many::<String>("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);

View file

@ -10,6 +10,7 @@ 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};
@ -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<String>,
@ -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_zero_flag(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<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
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,11 +452,13 @@ 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)
}
Mode::AllButLastLines(n) => read_but_last_n_lines(
&mut stdin,
n.try_into().unwrap(),
options.line_ending.into(),
),
}
}
(name, false) => {
@ -541,7 +529,7 @@ mod tests {
#[test]
fn test_args_modes() {
let args = options("-n -10M -vz").unwrap();
assert!(args.zeroed);
assert_eq!(args.line_ending, LineEnding::Nul);
assert!(args.verbose);
assert_eq!(args.mode, Mode::AllButLastLines(10 * 1024 * 1024));
}
@ -561,8 +549,11 @@ 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_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));
@ -579,7 +570,7 @@ mod tests {
assert!(!opts.verbose);
assert!(!opts.quiet);
assert!(!opts.zeroed);
assert_eq!(opts.line_ending, LineEnding::Newline);
assert_eq!(opts.mode, Mode::FirstLines(10));
assert!(opts.files.is_empty());
}
@ -631,17 +622,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);
}
}

View file

@ -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_zero_flag(state.zflag);
if state.cflag {
if state.selinux_supported {

View file

@ -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_zero_flag(matches.get_flag("z"));
let file1 = matches.get_one::<String>("file1").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};
#[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_zero_flag(options.get_flag(options::ZERO)),
})
}
}
@ -2173,7 +2170,7 @@ fn display_total(items: &[PathData], config: &Config, out: &mut BufWriter<Stdout
out,
"total {}{}",
display_size(total_size, config),
config.eol
config.line_ending
)?;
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.
// So we print a newline.
if current_col > 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,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.eol)?;
write!(
out,
" {} {}{}",
display_date(md, config),
dfn,
config.line_ending
)?;
} else {
#[cfg(unix)]
let leading_char = {

View file

@ -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<R: Read>(
reader: Option<&mut BufReader<R>>,
@ -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_zero_flag(matches.get_flag(options::ZERO_TERMINATED));
paste(files, serial, delimiters, line_ending)
}

View file

@ -14,6 +14,7 @@ use std::path::{Path, PathBuf};
use uucore::display::Quotable;
use uucore::error::{FromIo, UResult, USimpleError, UUsageError};
use uucore::fs::{canonicalize, MissingHandling, ResolveMode};
use uucore::line_ending::LineEnding;
use uucore::{format_usage, help_about, help_usage, show_error};
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");
no_trailing_delimiter = false;
}
let line_ending = if no_trailing_delimiter {
None
} else {
Some(LineEnding::from_zero_flag(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,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();
if no_trailing_delimiter {
print!("{path}");
} else if use_zero {
print!("{path}\0");
} else {
println!("{path}");
print!("{path}");
if let Some(line_ending) = line_ending {
print!("{line_ending}");
}
stdout().flush()
}

View file

@ -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_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) {
@ -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(())
}

View file

@ -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 {

View file

@ -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.

View file

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

View file

@ -45,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};
@ -306,7 +307,7 @@ pub struct GlobalSettings {
selectors: Vec<FieldSelector>,
separator: Option<char>,
threads: String,
zero_terminated: bool,
line_ending: LineEnding,
buffer_size: usize,
compress_prog: Option<String>,
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,14 +527,11 @@ impl<'a> Line<'a> {
}
fn print(&self, writer: &mut impl Write, settings: &GlobalSettings) {
if settings.zero_terminated && !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 {
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();
}
}
@ -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.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::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;

View file

@ -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;

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
}
}
}