From 2e60c32cbf9273e985891aa6df70f09ff69ce3d9 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Mon, 14 Aug 2023 13:40:05 +0200 Subject: [PATCH] nl: implement -d/--section-delimiter --- src/uu/nl/src/helper.rs | 3 +++ src/uu/nl/src/nl.rs | 54 ++++++++++++++++++++++++++++++------- tests/by-util/test_nl.rs | 57 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 104 insertions(+), 10 deletions(-) diff --git a/src/uu/nl/src/helper.rs b/src/uu/nl/src/helper.rs index a9dbbad798d..ad018154cd9 100644 --- a/src/uu/nl/src/helper.rs +++ b/src/uu/nl/src/helper.rs @@ -28,6 +28,9 @@ pub fn parse_options(settings: &mut crate::Settings, opts: &clap::ArgMatches) -> // This vector holds error messages encountered. let mut errs: Vec = vec![]; settings.renumber = opts.get_flag(options::NO_RENUMBER); + if let Some(delimiter) = opts.get_one::(options::SECTION_DELIMITER) { + settings.section_delimiter = delimiter.to_owned(); + } if let Some(val) = opts.get_one::(options::NUMBER_SEPARATOR) { settings.number_separator = val.to_owned(); } diff --git a/src/uu/nl/src/nl.rs b/src/uu/nl/src/nl.rs index 95121eb08d6..bb266afb3c3 100644 --- a/src/uu/nl/src/nl.rs +++ b/src/uu/nl/src/nl.rs @@ -26,7 +26,7 @@ pub struct Settings { body_numbering: NumberingStyle, footer_numbering: NumberingStyle, // The variable corresponding to -d - section_delimiter: [char; 2], + section_delimiter: String, // The variables corresponding to the options -v, -i, -l, -w. starting_line_number: i64, line_increment: i64, @@ -46,7 +46,7 @@ impl Default for Settings { header_numbering: NumberingStyle::None, body_numbering: NumberingStyle::NonEmpty, footer_numbering: NumberingStyle::None, - section_delimiter: ['\\', ':'], + section_delimiter: String::from("\\:"), starting_line_number: 1, line_increment: 1, join_blank_lines: 1, @@ -106,6 +106,42 @@ impl NumberFormat { } } +enum SectionDelimiter { + Header, + Body, + Footer, + None, +} + +struct SectionDelimiterParser { + delimiter: String, +} + +impl SectionDelimiterParser { + fn new(delimiter: &str) -> Self { + let delimiter = if delimiter.chars().count() == 1 { + format!("{delimiter}:") + } else { + delimiter.to_owned() + }; + + Self { delimiter } + } + + fn parse(&self, s: &str) -> SectionDelimiter { + if self.delimiter.is_empty() { + return SectionDelimiter::None; + } + + match s { + _ if s == self.delimiter.repeat(3) => SectionDelimiter::Header, + _ if s == self.delimiter.repeat(2) => SectionDelimiter::Body, + _ if s == self.delimiter => SectionDelimiter::Footer, + _ => SectionDelimiter::None, + } + } +} + pub mod options { pub const HELP: &str = "help"; pub const FILE: &str = "file"; @@ -275,6 +311,7 @@ fn nl(reader: &mut BufReader, settings: &Settings) -> UResult<()> { let mut current_numbering_style = &settings.body_numbering; let mut line_no = settings.starting_line_number; let mut consecutive_empty_lines = 0; + let section_delimiter_parser = SectionDelimiterParser::new(&settings.section_delimiter); for line in reader.lines() { let line = line.map_err_context(|| "could not read line".to_string())?; @@ -285,14 +322,11 @@ fn nl(reader: &mut BufReader, settings: &Settings) -> UResult<()> { consecutive_empty_lines = 0; }; - // FIXME section delimiters are hardcoded and settings.section_delimiter is ignored - // because --section-delimiter is not correctly implemented yet - let _ = settings.section_delimiter; // XXX suppress "field never read" warning - let new_numbering_style = match line.as_str() { - "\\:\\:\\:" => Some(&settings.header_numbering), - "\\:\\:" => Some(&settings.body_numbering), - "\\:" => Some(&settings.footer_numbering), - _ => None, + let new_numbering_style = match section_delimiter_parser.parse(&line) { + SectionDelimiter::Header => Some(&settings.header_numbering), + SectionDelimiter::Body => Some(&settings.body_numbering), + SectionDelimiter::Footer => Some(&settings.footer_numbering), + SectionDelimiter::None => None, }; if let Some(new_style) = new_numbering_style { diff --git a/tests/by-util/test_nl.rs b/tests/by-util/test_nl.rs index fb04ba9ae1b..b75c2dc2205 100644 --- a/tests/by-util/test_nl.rs +++ b/tests/by-util/test_nl.rs @@ -426,3 +426,60 @@ fn test_numbering_matched_lines() { } } } + +#[test] +fn test_section_delimiter() { + for arg in ["-dabc", "--section-delimiter=abc"] { + new_ucmd!() + .arg(arg) + .pipe_in("a\nabcabcabc\nb") // header section + .succeeds() + .stdout_is(" 1\ta\n\n b\n"); + + new_ucmd!() + .arg(arg) + .pipe_in("a\nabcabc\nb") // body section + .succeeds() + .stdout_is(" 1\ta\n\n 1\tb\n"); + + new_ucmd!() + .arg(arg) + .pipe_in("a\nabc\nb") // footer section + .succeeds() + .stdout_is(" 1\ta\n\n b\n"); + } +} + +#[test] +fn test_one_char_section_delimiter_expansion() { + for arg in ["-da", "--section-delimiter=a"] { + new_ucmd!() + .arg(arg) + .pipe_in("a\na:a:a:\nb") // header section + .succeeds() + .stdout_is(" 1\ta\n\n b\n"); + + new_ucmd!() + .arg(arg) + .pipe_in("a\na:a:\nb") // body section + .succeeds() + .stdout_is(" 1\ta\n\n 1\tb\n"); + + new_ucmd!() + .arg(arg) + .pipe_in("a\na:\nb") // footer section + .succeeds() + .stdout_is(" 1\ta\n\n b\n"); + } +} + +#[test] +fn test_empty_section_delimiter() { + for arg in ["-d ''", "--section-delimiter=''"] { + new_ucmd!() + .arg(arg) + .pipe_in("a\n\nb") + .succeeds() + .stdout_is(" 1\ta\n \n 2\tb\n"); + } +}