diff --git a/src/uu/more/src/more.rs b/src/uu/more/src/more.rs index a03e64a499..cdf6c9e8b5 100644 --- a/src/uu/more/src/more.rs +++ b/src/uu/more/src/more.rs @@ -54,6 +54,7 @@ struct Options { clean_print: bool, from_line: usize, lines: Option, + pattern: Option, print_over: bool, silent: bool, squeeze: bool, @@ -75,10 +76,14 @@ impl Options { Some(number) if number > 1 => number - 1, _ => 0, }; + let pattern = matches + .get_one::(options::PATTERN) + .map(|s| s.to_owned()); Self { clean_print: matches.get_flag(options::CLEAN_PRINT), from_line, lines, + pattern, print_over: matches.get_flag(options::PRINT_OVER), silent: matches.get_flag(options::SILENT), squeeze: matches.get_flag(options::SQUEEZE), @@ -206,6 +211,15 @@ pub fn uu_app() -> Command { .action(ArgAction::SetTrue) .hide(true), ) + .arg( + Arg::new(options::PATTERN) + .short('P') + .long(options::PATTERN) + .allow_hyphen_values(true) + .required(false) + .value_name("pattern") + .help("Display file beginning from pattern match"), + ) .arg( Arg::new(options::FROM_LINE) .short('F') @@ -245,14 +259,6 @@ pub fn uu_app() -> Command { .long(options::NO_PAUSE) .help("Suppress pause after form feed"), ) - .arg( - Arg::new(options::PATTERN) - .short('P') - .allow_hyphen_values(true) - .required(false) - .takes_value(true) - .help("Display file beginning from pattern match"), - ) */ .arg( Arg::new(options::FILES) @@ -307,6 +313,17 @@ fn more( let mut pager = Pager::new(rows, lines, next_file, options); + if options.pattern.is_some() { + match search_pattern_in_file(&pager.lines, &options.pattern) { + Some(number) => pager.upper_mark = number, + None => { + execute!(stdout, terminal::Clear(terminal::ClearType::CurrentLine))?; + stdout.write_all("\rPattern not found\n".as_bytes())?; + pager.content_rows -= 1; + } + } + } + if multiple_file { execute!(stdout, terminal::Clear(terminal::ClearType::CurrentLine)).unwrap(); stdout.write_all( @@ -592,6 +609,19 @@ impl<'a> Pager<'a> { } } +fn search_pattern_in_file(lines: &[String], pattern: &Option) -> Option { + let pattern = pattern.clone().unwrap_or_default(); + if lines.is_empty() || pattern.is_empty() { + return None; + } + for (line_number, line) in lines.iter().enumerate() { + if line.contains(pattern.as_str()) { + return Some(line_number); + } + } + None +} + fn paging_add_back_message(options: &Options, stdout: &mut std::io::Stdout) -> UResult<()> { if options.lines.is_some() { execute!(stdout, MoveUp(1))?; @@ -640,7 +670,7 @@ fn break_line(line: &str, cols: usize) -> Vec { #[cfg(test)] mod tests { - use super::break_line; + use super::{break_line, search_pattern_in_file}; use unicode_width::UnicodeWidthStr; #[test] @@ -688,4 +718,53 @@ mod tests { // Each 👩🏻‍🔬 is 6 character width it break line to the closest number to 80 => 6 * 13 = 78 assert_eq!((78, 42), (widths[0], widths[1])); } + + #[test] + fn test_search_pattern_empty_lines() { + let lines = vec![]; + let pattern = Some(String::from("pattern")); + assert_eq!(None, search_pattern_in_file(&lines, &pattern)); + } + + #[test] + fn test_search_pattern_empty_pattern() { + let lines = vec![String::from("line1"), String::from("line2")]; + let pattern = None; + assert_eq!(None, search_pattern_in_file(&lines, &pattern)); + } + + #[test] + fn test_search_pattern_found_pattern() { + let lines = vec![ + String::from("line1"), + String::from("line2"), + String::from("pattern"), + ]; + let lines2 = vec![ + String::from("line1"), + String::from("line2"), + String::from("pattern"), + String::from("pattern2"), + ]; + let lines3 = vec![ + String::from("line1"), + String::from("line2"), + String::from("other_pattern"), + ]; + let pattern = Some(String::from("pattern")); + assert_eq!(2, search_pattern_in_file(&lines, &pattern).unwrap()); + assert_eq!(2, search_pattern_in_file(&lines2, &pattern).unwrap()); + assert_eq!(2, search_pattern_in_file(&lines3, &pattern).unwrap()); + } + + #[test] + fn test_search_pattern_not_found_pattern() { + let lines = vec![ + String::from("line1"), + String::from("line2"), + String::from("something"), + ]; + let pattern = Some(String::from("pattern")); + assert_eq!(None, search_pattern_in_file(&lines, &pattern)); + } } diff --git a/tests/by-util/test_more.rs b/tests/by-util/test_more.rs index 3e9c426ad6..3a5eb58b04 100644 --- a/tests/by-util/test_more.rs +++ b/tests/by-util/test_more.rs @@ -34,6 +34,9 @@ fn test_valid_arg() { new_ucmd!().arg("-F").arg("10").succeeds(); new_ucmd!().arg("--from-line").arg("0").succeeds(); + + new_ucmd!().arg("-P").arg("something").succeeds(); + new_ucmd!().arg("--pattern").arg("-1").succeeds(); } } @@ -151,3 +154,50 @@ fn test_more_error_on_multiple_files() { .stderr_contains("file3"); } } + +#[test] +fn test_more_pattern_found() { + if std::io::stdout().is_terminal() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + let file = "test_file"; + + at.write(file, "line1\nline2"); + + // output only the second line "line2" + scene + .ucmd() + .arg("-P") + .arg("line2") + .arg(file) + .succeeds() + .no_stderr() + .stdout_does_not_contain("line1") + .stdout_contains("line2"); + } +} + +#[test] +fn test_more_pattern_not_found() { + if std::io::stdout().is_terminal() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + let file = "test_file"; + + let file_content = "line1\nline2"; + at.write(file, file_content); + + scene + .ucmd() + .arg("-P") + .arg("something") + .arg(file) + .succeeds() + .no_stderr() + .stdout_contains("Pattern not found") + .stdout_contains("line1") + .stdout_contains("line2"); + } +}