Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

expand: remove crash! macro #5510

Merged
merged 4 commits into from
Nov 10, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
189 changes: 99 additions & 90 deletions src/uu/expand/src/expand.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
use unicode_width::UnicodeWidthChar;
use uucore::display::Quotable;
use uucore::error::{FromIo, UError, UResult};
use uucore::{crash, format_usage, help_about, help_usage};
use uucore::{format_usage, help_about, help_usage};

const ABOUT: &str = help_about!("expand.md");
const USAGE: &str = help_usage!("expand.md");
Expand Down Expand Up @@ -265,7 +265,7 @@

let matches = uu_app().try_get_matches_from(expand_shortcuts(&args))?;

expand(&Options::new(&matches)?).map_err_context(|| "failed to write output".to_string())
expand(&Options::new(&matches)?)
}

pub fn uu_app() -> Command {
Expand Down Expand Up @@ -308,16 +308,13 @@
)
}

fn open(path: &str) -> BufReader<Box<dyn Read + 'static>> {
fn open(path: &str) -> UResult<BufReader<Box<dyn Read + 'static>>> {
let file_buf;
if path == "-" {
BufReader::new(Box::new(stdin()) as Box<dyn Read>)
Ok(BufReader::new(Box::new(stdin()) as Box<dyn Read>))
} else {
file_buf = match File::open(path) {
Ok(a) => a,
Err(e) => crash!(1, "{}: {}\n", path.maybe_quote(), e),
};
BufReader::new(Box::new(file_buf) as Box<dyn Read>)
file_buf = File::open(path).map_err_context(|| path.to_string())?;
Ok(BufReader::new(Box::new(file_buf) as Box<dyn Read>))
}
}

Expand Down Expand Up @@ -370,99 +367,111 @@
}

#[allow(clippy::cognitive_complexity)]
fn expand(options: &Options) -> std::io::Result<()> {
fn expand_line(
ceteece marked this conversation as resolved.
Show resolved Hide resolved
buf: &mut Vec<u8>,
output: &mut BufWriter<std::io::Stdout>,
tabstops: &[usize],
options: &Options,
) -> std::io::Result<()> {
use self::CharType::*;

let mut col = 0;
let mut byte = 0;
let mut init = true;

while byte < buf.len() {
let (ctype, cwidth, nbytes) = if options.uflag {
let nbytes = char::from(buf[byte]).len_utf8();

if byte + nbytes > buf.len() {
// don't overrun buffer because of invalid UTF-8
(Other, 1, 1)

Check warning on line 388 in src/uu/expand/src/expand.rs

View check run for this annotation

Codecov / codecov/patch

src/uu/expand/src/expand.rs#L388

Added line #L388 was not covered by tests
} else if let Ok(t) = from_utf8(&buf[byte..byte + nbytes]) {
match t.chars().next() {
Some('\t') => (Tab, 0, nbytes),
Some('\x08') => (Backspace, 0, nbytes),
Some(c) => (Other, UnicodeWidthChar::width(c).unwrap_or(0), nbytes),
None => {
// no valid char at start of t, so take 1 byte
(Other, 1, 1)

Check warning on line 396 in src/uu/expand/src/expand.rs

View check run for this annotation

Codecov / codecov/patch

src/uu/expand/src/expand.rs#L396

Added line #L396 was not covered by tests
}
}
} else {
(Other, 1, 1) // implicit assumption: non-UTF-8 char is 1 col wide

Check warning on line 400 in src/uu/expand/src/expand.rs

View check run for this annotation

Codecov / codecov/patch

src/uu/expand/src/expand.rs#L400

Added line #L400 was not covered by tests
}
} else {
(

Check warning on line 403 in src/uu/expand/src/expand.rs

View check run for this annotation

Codecov / codecov/patch

src/uu/expand/src/expand.rs#L403

Added line #L403 was not covered by tests
match buf[byte] {
// always take exactly 1 byte in strict ASCII mode
0x09 => Tab,
0x08 => Backspace,
_ => Other,

Check warning on line 408 in src/uu/expand/src/expand.rs

View check run for this annotation

Codecov / codecov/patch

src/uu/expand/src/expand.rs#L406-L408

Added lines #L406 - L408 were not covered by tests
},
1,
1,
)
};

// figure out how many columns this char takes up
match ctype {
Tab => {
// figure out how many spaces to the next tabstop
let nts = next_tabstop(tabstops, col, &options.remaining_mode);
col += nts;

// now dump out either spaces if we're expanding, or a literal tab if we're not
if init || !options.iflag {
if nts <= options.tspaces.len() {
output.write_all(options.tspaces[..nts].as_bytes())?;
} else {
output.write_all(" ".repeat(nts).as_bytes())?;
};
} else {
output.write_all(&buf[byte..byte + nbytes])?;
}
}
_ => {
col = if ctype == Other {
col + cwidth
} else if col > 0 {
col - 1
} else {
0
};

// if we're writing anything other than a space, then we're
// done with the line's leading spaces
if buf[byte] != 0x20 {
init = false;
}

output.write_all(&buf[byte..byte + nbytes])?;
}
}

byte += nbytes; // advance the pointer
}

output.flush()?;
buf.truncate(0); // clear the buffer

Ok(())
}

fn expand(options: &Options) -> UResult<()> {
let mut output = BufWriter::new(stdout());
let ts = options.tabstops.as_ref();
let mut buf = Vec::new();

for file in &options.files {
let mut fh = open(file);
let mut fh = open(file)?;

while match fh.read_until(b'\n', &mut buf) {
Ok(s) => s > 0,
Err(_) => buf.is_empty(),
} {
let mut col = 0;
let mut byte = 0;
let mut init = true;

while byte < buf.len() {
let (ctype, cwidth, nbytes) = if options.uflag {
let nbytes = char::from(buf[byte]).len_utf8();

if byte + nbytes > buf.len() {
// don't overrun buffer because of invalid UTF-8
(Other, 1, 1)
} else if let Ok(t) = from_utf8(&buf[byte..byte + nbytes]) {
match t.chars().next() {
Some('\t') => (Tab, 0, nbytes),
Some('\x08') => (Backspace, 0, nbytes),
Some(c) => (Other, UnicodeWidthChar::width(c).unwrap_or(0), nbytes),
None => {
// no valid char at start of t, so take 1 byte
(Other, 1, 1)
}
}
} else {
(Other, 1, 1) // implicit assumption: non-UTF-8 char is 1 col wide
}
} else {
(
match buf[byte] {
// always take exactly 1 byte in strict ASCII mode
0x09 => Tab,
0x08 => Backspace,
_ => Other,
},
1,
1,
)
};

// figure out how many columns this char takes up
match ctype {
Tab => {
// figure out how many spaces to the next tabstop
let nts = next_tabstop(ts, col, &options.remaining_mode);
col += nts;

// now dump out either spaces if we're expanding, or a literal tab if we're not
if init || !options.iflag {
if nts <= options.tspaces.len() {
output.write_all(options.tspaces[..nts].as_bytes())?;
} else {
output.write_all(" ".repeat(nts).as_bytes())?;
};
} else {
output.write_all(&buf[byte..byte + nbytes])?;
}
}
_ => {
col = if ctype == Other {
col + cwidth
} else if col > 0 {
col - 1
} else {
0
};

// if we're writing anything other than a space, then we're
// done with the line's leading spaces
if buf[byte] != 0x20 {
init = false;
}

output.write_all(&buf[byte..byte + nbytes])?;
}
}

byte += nbytes; // advance the pointer
}

output.flush()?;
buf.truncate(0); // clear the buffer
expand_line(&mut buf, &mut output, ts, options)
.map_err_context(|| "failed to write output".to_string())?;

Check warning on line 474 in src/uu/expand/src/expand.rs

View check run for this annotation

Codecov / codecov/patch

src/uu/expand/src/expand.rs#L474

Added line #L474 was not covered by tests
}
}
Ok(())
Expand Down
Loading