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

Merge pull request #2109 from arijit79/implement-more

Rewrite more
This commit is contained in:
Sylvestre Ledru 2021-05-31 12:52:26 +02:00 committed by GitHub
commit 6141fdcc73
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 523 additions and 118 deletions

152
Cargo.lock generated
View file

@ -224,6 +224,7 @@ dependencies = [
name = "coreutils" name = "coreutils"
version = "0.0.6" version = "0.0.6"
dependencies = [ dependencies = [
"atty",
"conv", "conv",
"filetime", "filetime",
"glob 0.3.0", "glob 0.3.0",
@ -487,6 +488,53 @@ dependencies = [
"lazy_static", "lazy_static",
] ]
[[package]]
name = "crossterm"
version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c36c10130df424b2f3552fcc2ddcd9b28a27b1e54b358b45874f88d1ca6888c"
dependencies = [
"bitflags",
"crossterm_winapi",
"lazy_static",
"libc",
"mio",
"parking_lot",
"signal-hook",
"winapi 0.3.9",
]
[[package]]
name = "crossterm_winapi"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0da8964ace4d3e4a044fd027919b2237000b24315a37c916f61809f1ff2140b9"
dependencies = [
"winapi 0.3.9",
]
[[package]]
name = "csv"
version = "1.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22813a6dc45b335f9bade10bf7271dc477e81113e89eb251a0bc2a8a81c536e1"
dependencies = [
"bstr",
"csv-core",
"itoa",
"ryu",
"serde",
]
[[package]]
name = "csv-core"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90"
dependencies = [
"memchr 2.4.0",
]
[[package]] [[package]]
name = "ctor" name = "ctor"
version = "0.1.20" version = "0.1.20"
@ -713,6 +761,15 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46dbcb333e86939721589d25a3557e180b52778cb33c7fdfe9e0158ff790d5ec" checksum = "46dbcb333e86939721589d25a3557e180b52778cb33c7fdfe9e0158ff790d5ec"
[[package]]
name = "instant"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61124eeebbd69b8190558df225adf7e4caafce0d743919e5d6b19652314ec5ec"
dependencies = [
"cfg-if 1.0.0",
]
[[package]] [[package]]
name = "ioctl-sys" name = "ioctl-sys"
version = "0.5.2" version = "0.5.2"
@ -768,6 +825,15 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "lock_api"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0382880606dff6d15c9476c416d18690b72742aa7b605bb6dd6ec9030fbf07eb"
dependencies = [
"scopeguard",
]
[[package]] [[package]]
name = "log" name = "log"
version = "0.4.14" version = "0.4.14"
@ -828,6 +894,28 @@ dependencies = [
"autocfg", "autocfg",
] ]
[[package]]
name = "mio"
version = "0.7.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e50ae3f04d169fcc9bde0b547d1c205219b7157e07ded9c5aff03e0637cb3ed7"
dependencies = [
"libc",
"log",
"miow",
"ntapi",
"winapi 0.3.9",
]
[[package]]
name = "miow"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21"
dependencies = [
"winapi 0.3.9",
]
[[package]] [[package]]
name = "nix" name = "nix"
version = "0.13.1" version = "0.13.1"
@ -859,6 +947,15 @@ version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb"
[[package]]
name = "ntapi"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44"
dependencies = [
"winapi 0.3.9",
]
[[package]] [[package]]
name = "num-bigint" name = "num-bigint"
version = "0.4.0" version = "0.4.0"
@ -971,6 +1068,31 @@ dependencies = [
"winapi 0.3.9", "winapi 0.3.9",
] ]
[[package]]
name = "parking_lot"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb"
dependencies = [
"instant",
"lock_api",
"parking_lot_core",
]
[[package]]
name = "parking_lot_core"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa7a782938e745763fe6907fc6ba86946d72f49fe7e21de074e08128a99fb018"
dependencies = [
"cfg-if 1.0.0",
"instant",
"libc",
"redox_syscall 0.2.8",
"smallvec 1.6.1",
"winapi 0.3.9",
]
[[package]] [[package]]
name = "paste" name = "paste"
version = "0.1.18" version = "0.1.18"
@ -1372,6 +1494,26 @@ dependencies = [
"generic-array", "generic-array",
] ]
[[package]]
name = "signal-hook"
version = "0.1.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e31d442c16f047a671b5a71e2161d6e68814012b7f5379d269ebd915fac2729"
dependencies = [
"libc",
"mio",
"signal-hook-registry",
]
[[package]]
name = "signal-hook-registry"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16f1d0fef1604ba8f7a073c7e701f213e056707210e9020af4528e0101ce11a6"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "smallvec" name = "smallvec"
version = "0.6.14" version = "0.6.14"
@ -1381,6 +1523,12 @@ dependencies = [
"maybe-uninit", "maybe-uninit",
] ]
[[package]]
name = "smallvec"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e"
[[package]] [[package]]
name = "socket2" name = "socket2"
version = "0.3.19" version = "0.3.19"
@ -1840,7 +1988,7 @@ dependencies = [
"paste", "paste",
"quickcheck", "quickcheck",
"rand 0.7.3", "rand 0.7.3",
"smallvec", "smallvec 0.6.14",
"uucore", "uucore",
"uucore_procs", "uucore_procs",
] ]
@ -2067,7 +2215,9 @@ dependencies = [
name = "uu_more" name = "uu_more"
version = "0.0.6" version = "0.0.6"
dependencies = [ dependencies = [
"atty",
"clap", "clap",
"crossterm",
"nix 0.13.1", "nix 0.13.1",
"redox_syscall 0.1.57", "redox_syscall 0.1.57",
"redox_termios", "redox_termios",

View file

@ -348,6 +348,7 @@ time = "0.1"
unindent = "0.1" unindent = "0.1"
uucore = { version=">=0.0.8", package="uucore", path="src/uucore", features=["entries"] } uucore = { version=">=0.0.8", package="uucore", path="src/uucore", features=["entries"] }
walkdir = "2.2" walkdir = "2.2"
atty = "0.2.14"
[target.'cfg(unix)'.dev-dependencies] [target.'cfg(unix)'.dev-dependencies]
rust-users = { version="0.10", package="users" } rust-users = { version="0.10", package="users" }

View file

@ -18,6 +18,8 @@ path = "src/more.rs"
clap = "2.33" clap = "2.33"
uucore = { version = ">=0.0.7", package = "uucore", path = "../../uucore" } uucore = { version = ">=0.0.7", package = "uucore", path = "../../uucore" }
uucore_procs = { version = ">=0.0.5", package = "uucore_procs", path = "../../uucore_procs" } uucore_procs = { version = ">=0.0.5", package = "uucore_procs", path = "../../uucore_procs" }
crossterm = ">=0.19"
atty = "0.2.14"
[target.'cfg(target_os = "redox")'.dependencies] [target.'cfg(target_os = "redox")'.dependencies]
redox_termios = "0.1" redox_termios = "0.1"

View file

@ -10,150 +10,395 @@
#[macro_use] #[macro_use]
extern crate uucore; extern crate uucore;
use std::fs::File; use std::{
use std::io::{stdin, stdout, BufRead, BufReader, Read, Write}; convert::TryInto,
fs::File,
io::{stdin, stdout, BufReader, Read, Stdout, Write},
path::Path,
time::Duration,
};
#[cfg(all(unix, not(target_os = "fuchsia")))] #[cfg(all(unix, not(target_os = "fuchsia")))]
extern crate nix; extern crate nix;
#[cfg(all(unix, not(target_os = "fuchsia")))]
use nix::sys::termios::{self, LocalFlags, SetArg};
use uucore::InvalidEncodingHandling;
#[cfg(target_os = "redox")] use clap::{App, Arg};
extern crate redox_termios; use crossterm::{
#[cfg(target_os = "redox")] event::{self, Event, KeyCode, KeyEvent, KeyModifiers},
extern crate syscall; execute, queue,
style::Attribute,
terminal,
};
use clap::{App, Arg, ArgMatches}; pub mod options {
pub const SILENT: &str = "silent";
static VERSION: &str = env!("CARGO_PKG_VERSION"); pub const LOGICAL: &str = "logical";
static ABOUT: &str = "A file perusal filter for CRT viewing."; pub const NO_PAUSE: &str = "no-pause";
pub const PRINT_OVER: &str = "print-over";
mod options { pub const CLEAN_PRINT: &str = "clean-print";
pub const FILE: &str = "file"; pub const SQUEEZE: &str = "squeeze";
pub const PLAIN: &str = "plain";
pub const LINES: &str = "lines";
pub const NUMBER: &str = "number";
pub const PATTERN: &str = "pattern";
pub const FROM_LINE: &str = "from-line";
pub const FILES: &str = "files";
} }
fn get_usage() -> String { const MULTI_FILE_TOP_PROMPT: &str = "::::::::::::::\n{}\n::::::::::::::\n";
format!("{} [options] <file>...", executable!())
}
pub fn uumain(args: impl uucore::Args) -> i32 { pub fn uumain(args: impl uucore::Args) -> i32 {
let usage = get_usage();
let args = args
.collect_str(InvalidEncodingHandling::ConvertLossy)
.accept_any();
let matches = App::new(executable!()) let matches = App::new(executable!())
.version(VERSION) .about("A file perusal filter for CRT viewing.")
.usage(usage.as_str()) .version(env!("CARGO_PKG_VERSION"))
.about(ABOUT) // The commented arguments below are unimplemented:
/*
.arg( .arg(
Arg::with_name(options::FILE) Arg::with_name(options::SILENT)
.number_of_values(1) .short("d")
.multiple(true), .long(options::SILENT)
.help("Display help instead of ringing bell"),
)
.arg(
Arg::with_name(options::LOGICAL)
.short("f")
.long(options::LOGICAL)
.help("Count logical rather than screen lines"),
)
.arg(
Arg::with_name(options::NO_PAUSE)
.short("l")
.long(options::NO_PAUSE)
.help("Suppress pause after form feed"),
)
.arg(
Arg::with_name(options::PRINT_OVER)
.short("c")
.long(options::PRINT_OVER)
.help("Do not scroll, display text and clean line ends"),
)
.arg(
Arg::with_name(options::CLEAN_PRINT)
.short("p")
.long(options::CLEAN_PRINT)
.help("Do not scroll, clean screen and display text"),
)
.arg(
Arg::with_name(options::SQUEEZE)
.short("s")
.long(options::SQUEEZE)
.help("Squeeze multiple blank lines into one"),
)
.arg(
Arg::with_name(options::PLAIN)
.short("u")
.long(options::PLAIN)
.help("Suppress underlining and bold"),
)
.arg(
Arg::with_name(options::LINES)
.short("n")
.long(options::LINES)
.value_name("number")
.takes_value(true)
.help("The number of lines per screenful"),
)
.arg(
Arg::with_name(options::NUMBER)
.allow_hyphen_values(true)
.long(options::NUMBER)
.required(false)
.takes_value(true)
.help("Same as --lines"),
)
.arg(
Arg::with_name(options::FROM_LINE)
.short("F")
.allow_hyphen_values(true)
.required(false)
.takes_value(true)
.value_name("number")
.help("Display file beginning from line number"),
)
.arg(
Arg::with_name(options::PATTERN)
.short("P")
.allow_hyphen_values(true)
.required(false)
.takes_value(true)
.help("Display file beginning from pattern match"),
)
*/
.arg(
Arg::with_name(options::FILES)
.required(false)
.multiple(true)
.help("Path to the files to be read"),
) )
.get_matches_from(args); .get_matches_from(args);
// FixME: fail without panic for now; but `more` should work with no arguments (ie, for piped input) let mut buff = String::new();
if let None | Some("-") = matches.value_of(options::FILE) { if let Some(filenames) = matches.values_of(options::FILES) {
show_usage_error!("Reading from stdin isn't supported yet."); let mut stdout = setup_term();
return 1; let length = filenames.len();
} for (idx, fname) in filenames.enumerate() {
let fname = Path::new(fname);
if let Some(x) = matches.value_of(options::FILE) { if fname.is_dir() {
let path = std::path::Path::new(x); terminal::disable_raw_mode().unwrap();
if path.is_dir() { show_usage_error!("'{}' is a directory.", fname.display());
show_usage_error!("'{}' is a directory.", x); return 1;
return 1; }
if !fname.exists() {
terminal::disable_raw_mode().unwrap();
show_error!(
"cannot open {}: No such file or directory",
fname.display()
);
return 1;
}
if length > 1 {
buff.push_str(&MULTI_FILE_TOP_PROMPT.replace("{}", fname.to_str().unwrap()));
}
let mut reader = BufReader::new(File::open(fname).unwrap());
reader.read_to_string(&mut buff).unwrap();
let is_last = idx + 1 == length;
more(&buff, &mut stdout, is_last);
buff.clear();
} }
reset_term(&mut stdout);
} else if atty::isnt(atty::Stream::Stdin) {
stdin().read_to_string(&mut buff).unwrap();
let mut stdout = setup_term();
more(&buff, &mut stdout, true);
reset_term(&mut stdout);
} else {
show_usage_error!("bad usage");
} }
more(matches);
0 0
} }
#[cfg(all(unix, not(target_os = "fuchsia")))] #[cfg(not(target_os = "fuchsia"))]
fn setup_term() -> termios::Termios { fn setup_term() -> std::io::Stdout {
let mut term = termios::tcgetattr(0).unwrap(); let stdout = stdout();
// Unset canonical mode, so we get characters immediately terminal::enable_raw_mode().unwrap();
term.local_flags.remove(LocalFlags::ICANON); stdout
// Disable local echo
term.local_flags.remove(LocalFlags::ECHO);
termios::tcsetattr(0, SetArg::TCSADRAIN, &term).unwrap();
term
} }
#[cfg(any(windows, target_os = "fuchsia"))] #[cfg(target_os = "fuchsia")]
#[inline(always)] #[inline(always)]
fn setup_term() -> usize { fn setup_term() -> usize {
0 0
} }
#[cfg(target_os = "redox")] #[cfg(not(target_os = "fuchsia"))]
fn setup_term() -> redox_termios::Termios { fn reset_term(stdout: &mut std::io::Stdout) {
let mut term = redox_termios::Termios::default(); terminal::disable_raw_mode().unwrap();
let fd = syscall::dup(0, b"termios").unwrap(); // Clear the prompt
syscall::read(fd, &mut term).unwrap(); queue!(stdout, terminal::Clear(terminal::ClearType::CurrentLine)).unwrap();
term.local_flags &= !redox_termios::ICANON; // Move cursor to the beginning without printing new line
term.local_flags &= !redox_termios::ECHO; print!("\r");
syscall::write(fd, &term).unwrap(); stdout.flush().unwrap();
let _ = syscall::close(fd);
term
} }
#[cfg(all(unix, not(target_os = "fuchsia")))] #[cfg(target_os = "fuchsia")]
fn reset_term(term: &mut termios::Termios) {
term.local_flags.insert(LocalFlags::ICANON);
term.local_flags.insert(LocalFlags::ECHO);
termios::tcsetattr(0, SetArg::TCSADRAIN, &term).unwrap();
}
#[cfg(any(windows, target_os = "fuchsia"))]
#[inline(always)] #[inline(always)]
fn reset_term(_: &mut usize) {} fn reset_term(_: &mut usize) {}
#[cfg(any(target_os = "redox"))] fn more(buff: &str, mut stdout: &mut Stdout, is_last: bool) {
fn reset_term(term: &mut redox_termios::Termios) { let (cols, rows) = terminal::size().unwrap();
let fd = syscall::dup(0, b"termios").unwrap(); let lines = break_buff(buff, usize::from(cols));
syscall::read(fd, term).unwrap(); let line_count: u16 = lines.len().try_into().unwrap();
term.local_flags |= redox_termios::ICANON;
term.local_flags |= redox_termios::ECHO;
syscall::write(fd, &term).unwrap();
let _ = syscall::close(fd);
}
fn more(matches: ArgMatches) { let mut upper_mark = 0;
let mut f: Box<dyn BufRead> = match matches.value_of(options::FILE) { let mut lines_left = line_count.saturating_sub(upper_mark + rows);
None | Some("-") => Box::new(BufReader::new(stdin())),
Some(filename) => Box::new(BufReader::new(File::open(filename).unwrap())),
};
let mut buffer = [0; 1024];
let mut term = setup_term(); draw(
&mut upper_mark,
rows,
&mut stdout,
lines.clone(),
line_count,
);
let mut end = false; // Specifies whether we have reached the end of the file and should
while let Ok(sz) = f.read(&mut buffer) { // return on the next keypress. However, we immediately return when
if sz == 0 { // this is the last file.
break; let mut to_be_done = false;
} if lines_left == 0 && is_last {
stdout().write_all(&buffer[0..sz]).unwrap(); if is_last {
for byte in std::io::stdin().bytes() { return;
match byte.unwrap() { } else {
b' ' => break, to_be_done = true;
b'q' | 27 => {
end = true;
break;
}
_ => (),
}
}
if end {
break;
} }
} }
loop {
if event::poll(Duration::from_millis(10)).unwrap() {
match event::read().unwrap() {
Event::Key(KeyEvent {
code: KeyCode::Char('q'),
modifiers: KeyModifiers::NONE,
})
| Event::Key(KeyEvent {
code: KeyCode::Char('c'),
modifiers: KeyModifiers::CONTROL,
}) => {
reset_term(&mut stdout);
std::process::exit(0);
}
Event::Key(KeyEvent {
code: KeyCode::Down,
modifiers: KeyModifiers::NONE,
})
| Event::Key(KeyEvent {
code: KeyCode::Char(' '),
modifiers: KeyModifiers::NONE,
}) => {
upper_mark = upper_mark.saturating_add(rows.saturating_sub(1));
}
Event::Key(KeyEvent {
code: KeyCode::Up,
modifiers: KeyModifiers::NONE,
}) => {
upper_mark = upper_mark.saturating_sub(rows.saturating_sub(1));
}
_ => continue,
}
lines_left = line_count.saturating_sub(upper_mark + rows);
draw(
&mut upper_mark,
rows,
&mut stdout,
lines.clone(),
line_count,
);
reset_term(&mut term); if lines_left == 0 {
println!(); if to_be_done || is_last {
return
}
to_be_done = true;
}
}
}
}
fn draw(
upper_mark: &mut u16,
rows: u16,
mut stdout: &mut std::io::Stdout,
lines: Vec<String>,
lc: u16,
) {
execute!(stdout, terminal::Clear(terminal::ClearType::CurrentLine)).unwrap();
let (up_mark, lower_mark) = calc_range(*upper_mark, rows, lc);
// Reduce the row by 1 for the prompt
let displayed_lines = lines
.iter()
.skip(up_mark.into())
.take(usize::from(rows.saturating_sub(1)));
for line in displayed_lines {
stdout
.write_all(format!("\r{}\n", line).as_bytes())
.unwrap();
}
make_prompt_and_flush(&mut stdout, lower_mark, lc);
*upper_mark = up_mark;
}
// Break the lines on the cols of the terminal
fn break_buff(buff: &str, cols: usize) -> Vec<String> {
let mut lines = Vec::new();
for l in buff.lines() {
lines.append(&mut break_line(l, cols));
}
lines
}
fn break_line(mut line: &str, cols: usize) -> Vec<String> {
let breaks = (line.len() / cols).saturating_add(1);
let mut lines = Vec::with_capacity(breaks);
// TODO: Use unicode width instead of the length in bytes.
if line.len() < cols {
lines.push(line.to_string());
return lines;
}
for _ in 1..=breaks {
let (line1, line2) = line.split_at(cols);
lines.push(line1.to_string());
if line2.len() < cols {
lines.push(line2.to_string());
break;
}
line = line2;
}
lines
}
// Calculate upper_mark based on certain parameters
fn calc_range(mut upper_mark: u16, rows: u16, line_count: u16) -> (u16, u16) {
let mut lower_mark = upper_mark.saturating_add(rows);
if lower_mark >= line_count {
upper_mark = line_count.saturating_sub(rows);
lower_mark = line_count;
} else {
lower_mark = lower_mark.saturating_sub(1)
}
(upper_mark, lower_mark)
}
// Make a prompt similar to original more
fn make_prompt_and_flush(stdout: &mut Stdout, lower_mark: u16, lc: u16) {
write!(
stdout,
"\r{}--More--({}%){}",
Attribute::Reverse,
((lower_mark as f64 / lc as f64) * 100.0).round() as u16,
Attribute::Reset
)
.unwrap();
stdout.flush().unwrap();
}
#[cfg(test)]
mod tests {
use super::{break_line, calc_range};
// It is good to test the above functions
#[test]
fn test_calc_range() {
assert_eq!((0, 24), calc_range(0, 25, 100));
assert_eq!((50, 74), calc_range(50, 25, 100));
assert_eq!((75, 100), calc_range(85, 25, 100));
}
#[test]
fn test_break_lines_long() {
let mut test_string = String::with_capacity(100);
for _ in 0..200 {
test_string.push('#');
}
let lines = break_line(&test_string, 80);
assert_eq!(
(80, 80, 40),
(lines[0].len(), lines[1].len(), lines[2].len())
);
}
#[test]
fn test_break_lines_short() {
let mut test_string = String::with_capacity(100);
for _ in 0..20 {
test_string.push('#');
}
let lines = break_line(&test_string, 80);
assert_eq!(20, lines[0].len());
}
} }

View file

@ -2,15 +2,22 @@ use crate::common::util::*;
#[test] #[test]
fn test_more_no_arg() { fn test_more_no_arg() {
// stderr = more: Reading from stdin isn't supported yet. // Reading from stdin is now supported, so this must succeed
new_ucmd!().fails(); if atty::is(atty::Stream::Stdout) {
new_ucmd!().succeeds();
} else {}
} }
#[test] #[test]
fn test_more_dir_arg() { fn test_more_dir_arg() {
let result = new_ucmd!().arg(".").run(); // Run the test only if there's a valud terminal, else do nothing
result.failure(); // Maybe we could capture the error, i.e. "Device not found" in that case
const EXPECTED_ERROR_MESSAGE: &str = // but I am leaving this for later
"more: '.' is a directory.\nTry 'more --help' for more information."; if atty::is(atty::Stream::Stdout) {
assert_eq!(result.stderr_str().trim(), EXPECTED_ERROR_MESSAGE); let result = new_ucmd!().arg(".").run();
result.failure();
const EXPECTED_ERROR_MESSAGE: &str =
"more: '.' is a directory.\nTry 'more --help' for more information.";
assert_eq!(result.stderr_str().trim(), EXPECTED_ERROR_MESSAGE);
} else {}
} }