Skip to content

Commit

Permalink
Extract uucore::line_ending::LineEnding
Browse files Browse the repository at this point in the history
Aims to provide consistent newline/zero terminator handling.
  • Loading branch information
simon04 committed Jul 29, 2023
1 parent 5c65f44 commit 2b97a94
Show file tree
Hide file tree
Showing 16 changed files with 124 additions and 164 deletions.
33 changes: 1 addition & 32 deletions src/uu/comm/src/comm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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<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>),
Expand Down
18 changes: 10 additions & 8 deletions src/uu/cut/src/cut.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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 {
Expand All @@ -42,7 +43,7 @@ struct FieldOptions {
delimiter: Delimiter,
out_delimiter: Option<String>,
only_delimited: bool,
zero_terminated: bool,
line_ending: LineEnding,
}

enum Mode {
Expand All @@ -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
Expand Down Expand Up @@ -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());
Expand Down Expand Up @@ -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)),
},
)
}),
Expand All @@ -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)),
},
)
}),
Expand All @@ -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::<String>(options::DELIMITER).map(|s| s.as_str()) {
Some(_) if whitespace_delimited => {
Expand Down Expand Up @@ -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,
},
))
}
Expand All @@ -455,7 +457,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
},
out_delimiter: out_delim,
only_delimited,
zero_terminated,
line_ending,
},
)),
}
Expand Down
15 changes: 8 additions & 7 deletions src/uu/env/src/env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand All @@ -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>,
Expand All @@ -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();
}
}

Expand All @@ -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(),
Expand Down Expand Up @@ -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::<String>("chdir").map(|s| s.as_str());
let files = match matches.get_many::<String>("file") {
Some(v) => v.map(|s| s.as_str()).collect(),
Expand All @@ -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,
Expand Down Expand Up @@ -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);
Expand Down
68 changes: 27 additions & 41 deletions src/uu/head/src/head.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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>,
Expand All @@ -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)?;
Expand Down Expand Up @@ -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`.
Expand Down Expand Up @@ -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(())
}
Expand Down Expand Up @@ -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,
{
Expand All @@ -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 {
Expand Down Expand Up @@ -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,
Expand All @@ -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),
}
Expand Down Expand Up @@ -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())
}
}
}
Expand Down Expand Up @@ -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));
}
Expand All @@ -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));
Expand All @@ -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());
}
Expand Down Expand Up @@ -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);
}
}
9 changes: 2 additions & 7 deletions src/uu/id/src/id.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand Down Expand Up @@ -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 {
Expand Down
Loading

0 comments on commit 2b97a94

Please sign in to comment.