mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-30 04:27:45 +00:00
unexpand: remove crash! macro (#5588)
* unexpand_macro_fixed * unexpand * Remove crash * Passed local test case * Small changes * unexpand: remove #[allow(clippy::cognitive_complexity)] --------- Co-authored-by: Daniel Hofstetter <daniel.hofstetter@42dh.com>
This commit is contained in:
parent
35ae43e71f
commit
052f38f14d
1 changed files with 95 additions and 97 deletions
|
@ -14,8 +14,8 @@ use std::num::IntErrorKind;
|
||||||
use std::str::from_utf8;
|
use std::str::from_utf8;
|
||||||
use unicode_width::UnicodeWidthChar;
|
use unicode_width::UnicodeWidthChar;
|
||||||
use uucore::display::Quotable;
|
use uucore::display::Quotable;
|
||||||
use uucore::error::{FromIo, UError, UResult};
|
use uucore::error::{FromIo, UError, UResult, USimpleError};
|
||||||
use uucore::{crash, crash_if_err, format_usage, help_about, help_usage};
|
use uucore::{crash_if_err, format_usage, help_about, help_usage};
|
||||||
|
|
||||||
const USAGE: &str = help_usage!("unexpand.md");
|
const USAGE: &str = help_usage!("unexpand.md");
|
||||||
const ABOUT: &str = help_about!("unexpand.md");
|
const ABOUT: &str = help_about!("unexpand.md");
|
||||||
|
@ -161,7 +161,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||||
|
|
||||||
let matches = uu_app().try_get_matches_from(expand_shortcuts(&args))?;
|
let matches = uu_app().try_get_matches_from(expand_shortcuts(&args))?;
|
||||||
|
|
||||||
unexpand(&Options::new(&matches)?).map_err_context(String::new)
|
unexpand(&Options::new(&matches)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn uu_app() -> Command {
|
pub fn uu_app() -> Command {
|
||||||
|
@ -209,16 +209,13 @@ pub fn uu_app() -> Command {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn open(path: &str) -> BufReader<Box<dyn Read + 'static>> {
|
fn open(path: &str) -> UResult<BufReader<Box<dyn Read + 'static>>> {
|
||||||
let file_buf;
|
let file_buf;
|
||||||
if path == "-" {
|
if path == "-" {
|
||||||
BufReader::new(Box::new(stdin()) as Box<dyn Read>)
|
Ok(BufReader::new(Box::new(stdin()) as Box<dyn Read>))
|
||||||
} else {
|
} else {
|
||||||
file_buf = match File::open(path) {
|
file_buf = File::open(path).map_err_context(|| path.to_string())?;
|
||||||
Ok(a) => a,
|
Ok(BufReader::new(Box::new(file_buf) as Box<dyn Read>))
|
||||||
Err(e) => crash!(1, "{}: {}", path.maybe_quote(), e),
|
|
||||||
};
|
|
||||||
BufReader::new(Box::new(file_buf) as Box<dyn Read>)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -315,106 +312,107 @@ fn next_char_info(uflag: bool, buf: &[u8], byte: usize) -> (CharType, usize, usi
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::cognitive_complexity)]
|
#[allow(clippy::cognitive_complexity)]
|
||||||
fn unexpand(options: &Options) -> std::io::Result<()> {
|
fn unexpand_line(
|
||||||
|
buf: &mut Vec<u8>,
|
||||||
|
output: &mut BufWriter<std::io::Stdout>,
|
||||||
|
options: &Options,
|
||||||
|
lastcol: usize,
|
||||||
|
ts: &[usize],
|
||||||
|
) -> std::io::Result<()> {
|
||||||
|
let mut byte = 0; // offset into the buffer
|
||||||
|
let mut col = 0; // the current column
|
||||||
|
let mut scol = 0; // the start col for the current span, i.e., the already-printed width
|
||||||
|
let mut init = true; // are we at the start of the line?
|
||||||
|
let mut pctype = CharType::Other;
|
||||||
|
|
||||||
|
while byte < buf.len() {
|
||||||
|
// when we have a finite number of columns, never convert past the last column
|
||||||
|
if lastcol > 0 && col >= lastcol {
|
||||||
|
write_tabs(output, ts, scol, col, pctype == CharType::Tab, init, true);
|
||||||
|
output.write_all(&buf[byte..])?;
|
||||||
|
scol = col;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// figure out how big the next char is, if it's UTF-8
|
||||||
|
let (ctype, cwidth, nbytes) = next_char_info(options.uflag, buf, byte);
|
||||||
|
|
||||||
|
// now figure out how many columns this char takes up, and maybe print it
|
||||||
|
let tabs_buffered = init || options.aflag;
|
||||||
|
match ctype {
|
||||||
|
CharType::Space | CharType::Tab => {
|
||||||
|
// compute next col, but only write space or tab chars if not buffering
|
||||||
|
col += if ctype == CharType::Space {
|
||||||
|
1
|
||||||
|
} else {
|
||||||
|
next_tabstop(ts, col).unwrap_or(1)
|
||||||
|
};
|
||||||
|
|
||||||
|
if !tabs_buffered {
|
||||||
|
output.write_all(&buf[byte..byte + nbytes])?;
|
||||||
|
scol = col; // now printed up to this column
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CharType::Other | CharType::Backspace => {
|
||||||
|
// always
|
||||||
|
write_tabs(
|
||||||
|
output,
|
||||||
|
ts,
|
||||||
|
scol,
|
||||||
|
col,
|
||||||
|
pctype == CharType::Tab,
|
||||||
|
init,
|
||||||
|
options.aflag,
|
||||||
|
);
|
||||||
|
init = false; // no longer at the start of a line
|
||||||
|
col = if ctype == CharType::Other {
|
||||||
|
// use computed width
|
||||||
|
col + cwidth
|
||||||
|
} else if col > 0 {
|
||||||
|
// Backspace case, but only if col > 0
|
||||||
|
col - 1
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
};
|
||||||
|
output.write_all(&buf[byte..byte + nbytes])?;
|
||||||
|
scol = col; // we've now printed up to this column
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
byte += nbytes; // move on to next char
|
||||||
|
pctype = ctype; // save the previous type
|
||||||
|
}
|
||||||
|
|
||||||
|
// write out anything remaining
|
||||||
|
write_tabs(output, ts, scol, col, pctype == CharType::Tab, init, true);
|
||||||
|
output.flush()?;
|
||||||
|
buf.truncate(0); // clear out the buffer
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unexpand(options: &Options) -> UResult<()> {
|
||||||
let mut output = BufWriter::new(stdout());
|
let mut output = BufWriter::new(stdout());
|
||||||
let ts = &options.tabstops[..];
|
let ts = &options.tabstops[..];
|
||||||
let mut buf = Vec::new();
|
let mut buf = Vec::new();
|
||||||
let lastcol = if ts.len() > 1 { *ts.last().unwrap() } else { 0 };
|
let lastcol = if ts.len() > 1 { *ts.last().unwrap() } else { 0 };
|
||||||
|
|
||||||
for file in &options.files {
|
for file in &options.files {
|
||||||
let mut fh = open(file);
|
let mut fh = match open(file) {
|
||||||
|
Ok(reader) => reader,
|
||||||
|
Err(err) => {
|
||||||
|
return Err(USimpleError::new(1, err.to_string()));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
while match fh.read_until(b'\n', &mut buf) {
|
while match fh.read_until(b'\n', &mut buf) {
|
||||||
Ok(s) => s > 0,
|
Ok(s) => s > 0,
|
||||||
Err(_) => !buf.is_empty(),
|
Err(_) => !buf.is_empty(),
|
||||||
} {
|
} {
|
||||||
let mut byte = 0; // offset into the buffer
|
unexpand_line(&mut buf, &mut output, options, lastcol, ts)?;
|
||||||
let mut col = 0; // the current column
|
|
||||||
let mut scol = 0; // the start col for the current span, i.e., the already-printed width
|
|
||||||
let mut init = true; // are we at the start of the line?
|
|
||||||
let mut pctype = CharType::Other;
|
|
||||||
|
|
||||||
while byte < buf.len() {
|
|
||||||
// when we have a finite number of columns, never convert past the last column
|
|
||||||
if lastcol > 0 && col >= lastcol {
|
|
||||||
write_tabs(
|
|
||||||
&mut output,
|
|
||||||
ts,
|
|
||||||
scol,
|
|
||||||
col,
|
|
||||||
pctype == CharType::Tab,
|
|
||||||
init,
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
output.write_all(&buf[byte..])?;
|
|
||||||
scol = col;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// figure out how big the next char is, if it's UTF-8
|
|
||||||
let (ctype, cwidth, nbytes) = next_char_info(options.uflag, &buf, byte);
|
|
||||||
|
|
||||||
// now figure out how many columns this char takes up, and maybe print it
|
|
||||||
let tabs_buffered = init || options.aflag;
|
|
||||||
match ctype {
|
|
||||||
CharType::Space | CharType::Tab => {
|
|
||||||
// compute next col, but only write space or tab chars if not buffering
|
|
||||||
col += if ctype == CharType::Space {
|
|
||||||
1
|
|
||||||
} else {
|
|
||||||
next_tabstop(ts, col).unwrap_or(1)
|
|
||||||
};
|
|
||||||
|
|
||||||
if !tabs_buffered {
|
|
||||||
output.write_all(&buf[byte..byte + nbytes])?;
|
|
||||||
scol = col; // now printed up to this column
|
|
||||||
}
|
|
||||||
}
|
|
||||||
CharType::Other | CharType::Backspace => {
|
|
||||||
// always
|
|
||||||
write_tabs(
|
|
||||||
&mut output,
|
|
||||||
ts,
|
|
||||||
scol,
|
|
||||||
col,
|
|
||||||
pctype == CharType::Tab,
|
|
||||||
init,
|
|
||||||
options.aflag,
|
|
||||||
);
|
|
||||||
init = false; // no longer at the start of a line
|
|
||||||
col = if ctype == CharType::Other {
|
|
||||||
// use computed width
|
|
||||||
col + cwidth
|
|
||||||
} else if col > 0 {
|
|
||||||
// Backspace case, but only if col > 0
|
|
||||||
col - 1
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
};
|
|
||||||
output.write_all(&buf[byte..byte + nbytes])?;
|
|
||||||
scol = col; // we've now printed up to this column
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
byte += nbytes; // move on to next char
|
|
||||||
pctype = ctype; // save the previous type
|
|
||||||
}
|
|
||||||
|
|
||||||
// write out anything remaining
|
|
||||||
write_tabs(
|
|
||||||
&mut output,
|
|
||||||
ts,
|
|
||||||
scol,
|
|
||||||
col,
|
|
||||||
pctype == CharType::Tab,
|
|
||||||
init,
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
output.flush()?;
|
|
||||||
buf.truncate(0); // clear out the buffer
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
output.flush()
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue