diff --git a/src/bin/tuc.rs b/src/bin/tuc.rs index a38e58e..3e1f1ba 100644 --- a/src/bin/tuc.rs +++ b/src/bin/tuc.rs @@ -114,7 +114,7 @@ fn parse_args() -> Result { }; if bounds_type == BoundsType::Fields - && (maybe_fields.is_none() || maybe_fields.as_ref().unwrap().0.is_empty()) + && (maybe_fields.is_none() || maybe_fields.as_ref().unwrap().is_empty()) { eprintln!("tuc: invariant error. At this point we expected to find at least 1 field bound"); std::process::exit(1); @@ -209,12 +209,7 @@ fn parse_args() -> Result { .or(maybe_lines) .unwrap(); - if has_json - && bounds - .0 - .iter() - .any(|s| matches!(s, BoundOrFiller::Filler(_))) - { + if has_json && bounds.iter().any(|s| matches!(s, BoundOrFiller::Filler(_))) { eprintln!("tuc: runtime error. Cannot format fields when using --json"); std::process::exit(1); } diff --git a/src/bounds.rs b/src/bounds.rs index 6258889..d0aec39 100644 --- a/src/bounds.rs +++ b/src/bounds.rs @@ -95,21 +95,65 @@ pub fn parse_bounds_list(s: &str) -> Result> { } } -#[derive(Debug)] -pub struct UserBoundsList(pub Vec); +#[derive(Debug, Clone)] +pub struct UserBoundsList { + pub list: Vec, + /// Optimization that we can use to stop searching for fields. + /// It's available only when every bound uses positive indexes. + /// When conditions do not apply, its value is `Side::Continue`. + pub last_interesting_field: Side, +} impl Deref for UserBoundsList { type Target = Vec; fn deref(&self) -> &Self::Target { - &self.0 + &self.list + } +} + +impl From> for UserBoundsList { + fn from(list: Vec) -> Self { + let mut ubl = UserBoundsList { + list, + last_interesting_field: Side::Continue, + }; + + let mut rightmost_bound: Option = None; + let mut last_bound: Option<&mut UserBounds> = None; + + let is_sortable = ubl.is_sortable(); + + ubl.list.iter_mut().for_each(|bof| { + if let BoundOrFiller::Bound(b) = bof { + if rightmost_bound.is_none() || b.r > rightmost_bound.unwrap() { + rightmost_bound = Some(b.r); + } + + last_bound = Some(b); + } + }); + + if !is_sortable { + rightmost_bound = None; + } + + last_bound + .expect("UserBoundsList must contain at least one UserBounds") + .is_last = true; + + ubl.last_interesting_field = rightmost_bound.unwrap_or(Side::Continue); + ubl } } impl FromStr for UserBoundsList { type Err = anyhow::Error; fn from_str(s: &str) -> Result { - Ok(UserBoundsList(parse_bounds_list(s)?)) + if s.trim().is_empty() { + bail!("UserBoundsList must contain at least one UserBounds"); + } + Ok(parse_bounds_list(s)?.into()) } } @@ -142,7 +186,7 @@ impl UserBoundsList { } fn get_userbounds_only(&self) -> impl Iterator + '_ { - self.0.iter().flat_map(|b| match b { + self.list.iter().flat_map(|b| match b { BoundOrFiller::Bound(x) => Some(x), _ => None, }) @@ -192,17 +236,18 @@ impl UserBoundsList { * and with every ranged bound converted into single slot bounds. */ pub fn unpack(&self, num_fields: usize) -> UserBoundsList { - UserBoundsList( - self.0 - .iter() - .filter_map(|x| match x { - BoundOrFiller::Filler(_) => None, - BoundOrFiller::Bound(b) => Some(b.unpack(num_fields)), - }) - .flatten() - .map(BoundOrFiller::Bound) - .collect(), - ) + let list: Vec = self + .list + .iter() + .filter_map(|x| match x { + BoundOrFiller::Filler(_) => None, + BoundOrFiller::Bound(b) => Some(b.unpack(num_fields)), + }) + .flatten() + .map(BoundOrFiller::Bound) + .collect(); + + list.into() } } @@ -256,6 +301,7 @@ impl PartialOrd for Side { pub struct UserBounds { pub l: Side, pub r: Side, + pub is_last: bool, } impl fmt::Display for UserBounds { @@ -314,9 +360,20 @@ impl FromStr for UserBounds { } } -impl UserBounds { - pub fn new(l: Side, r: Side) -> Self { - UserBounds { l, r } +pub trait UserBoundsTrait { + fn new(l: Side, r: Side) -> Self; + fn try_into_range(&self, parts_length: usize) -> Result>; + fn matches(&self, idx: T) -> Result; + fn unpack(&self, num_fields: usize) -> Vec; +} + +impl UserBoundsTrait for UserBounds { + fn new(l: Side, r: Side) -> Self { + UserBounds { + l, + r, + is_last: false, + } } /** * Check if a field is between the bounds. @@ -328,7 +385,7 @@ impl UserBounds { * Fields are 1-indexed. */ #[inline(always)] - pub fn matches(&self, idx: i32) -> Result { + fn matches(&self, idx: i32) -> Result { match (self.l, self.r) { (Side::Some(left), _) if (left * idx).is_negative() => { bail!( @@ -363,21 +420,22 @@ impl UserBounds { /// e.g. /// /// ```rust - /// # use tuc::bounds::UserBounds; + /// # use tuc::bounds::{UserBounds, UserBoundsTrait}; /// # use std::ops::Range; /// # use tuc::bounds::Side; + /// # use std::str::FromStr; /// /// assert_eq!( - /// (UserBounds { l: Side::Some(1), r: Side::Some(2) }).try_into_range(5).unwrap(), + /// UserBounds::from_str("1:2").unwrap().try_into_range(5).unwrap(), /// Range { start: 0, end: 2} // 2, not 1, because it's exclusive /// ); /// /// assert_eq!( - /// (UserBounds { l: Side::Some(1), r: Side::Continue }).try_into_range(5).unwrap(), + /// UserBounds::from_str("1:").unwrap().try_into_range(5).unwrap(), /// Range { start: 0, end: 5} /// ); /// ``` - pub fn try_into_range(&self, parts_length: usize) -> Result> { + fn try_into_range(&self, parts_length: usize) -> Result> { let start: usize = match self.l { Side::Continue => 0, Side::Some(v) => { @@ -414,11 +472,9 @@ impl UserBounds { Ok(Range { start, end }) } - /** - * Transform a ranged bound into a list of one or more - * 1 slot bound - */ - pub fn unpack(&self, num_fields: usize) -> Vec { + /// Transform a ranged bound into a list of one or more + /// slot bound + fn unpack(&self, num_fields: usize) -> Vec { let mut bounds = Vec::new(); let n: i32 = num_fields .try_into() @@ -714,9 +770,12 @@ mod tests { } #[test] - fn test_user_bounds_is_sortable() { - assert!(UserBoundsList(Vec::new()).is_sortable()); + fn test_user_bounds_cannot_be_empty() { + assert!(UserBoundsList::from_str("").is_err()); + } + #[test] + fn test_user_bounds_is_sortable() { assert!(UserBoundsList::from_str("1").unwrap().is_sortable()); assert!(UserBoundsList::from_str("1,2").unwrap().is_sortable()); @@ -732,8 +791,6 @@ mod tests { #[test] fn test_vec_of_bounds_is_sorted() { - assert!(UserBoundsList::from_str("").unwrap().is_sorted()); - assert!(UserBoundsList::from_str("1").unwrap().is_sorted()); assert!(UserBoundsList::from_str("1,2").unwrap().is_sorted()); @@ -763,7 +820,10 @@ mod tests { #[test] fn test_vec_of_bounds_can_unpack() { assert_eq!( - UserBoundsList::from_str("1,:1,2:3,4:").unwrap().unpack(4).0, + UserBoundsList::from_str("1,:1,2:3,4:") + .unwrap() + .unpack(4) + .list, vec![ BoundOrFiller::Bound(UserBounds::new(Side::Some(1), Side::Some(1))), BoundOrFiller::Bound(UserBounds::new(Side::Some(1), Side::Some(1))), @@ -774,7 +834,10 @@ mod tests { ); assert_eq!( - UserBoundsList::from_str("a{1}b{2}c").unwrap().unpack(4).0, + UserBoundsList::from_str("a{1}b{2}c") + .unwrap() + .unpack(4) + .list, vec![ BoundOrFiller::Bound(UserBounds::new(Side::Some(1), Side::Some(1))), BoundOrFiller::Bound(UserBounds::new(Side::Some(2), Side::Some(2))), diff --git a/src/cut_bytes.rs b/src/cut_bytes.rs index a80f20d..c856128 100644 --- a/src/cut_bytes.rs +++ b/src/cut_bytes.rs @@ -1,7 +1,7 @@ use anyhow::Result; use std::io::{Read, Write}; -use crate::bounds::BoundOrFiller; +use crate::bounds::{BoundOrFiller, UserBoundsTrait}; use crate::options::Opt; use crate::read_utils::read_bytes_to_end; @@ -10,7 +10,7 @@ fn cut_bytes(data: &[u8], opt: &Opt, stdout: &mut W) -> Result<()> { return Ok(()); } - opt.bounds.0.iter().try_for_each(|bof| -> Result<()> { + opt.bounds.iter().try_for_each(|bof| -> Result<()> { let output = match bof { BoundOrFiller::Bound(b) => { let r = b.try_into_range(data.len())?; diff --git a/src/cut_lines.rs b/src/cut_lines.rs index 4a303c7..f781014 100644 --- a/src/cut_lines.rs +++ b/src/cut_lines.rs @@ -2,7 +2,7 @@ use anyhow::{bail, Result}; use std::io::{BufRead, Write}; use std::ops::Range; -use crate::bounds::{BoundOrFiller, Side}; +use crate::bounds::{BoundOrFiller, Side, UserBoundsTrait}; use crate::cut_str::cut_str; use crate::options::Opt; use crate::read_utils::read_line_with_eol; @@ -131,8 +131,10 @@ pub fn read_and_cut_lines( #[cfg(test)] mod tests { + use std::str::FromStr; + use crate::{ - bounds::{BoundsType, UserBounds, UserBoundsList}, + bounds::{BoundsType, UserBoundsList}, options::EOL, }; @@ -147,40 +149,10 @@ mod tests { } } - const BOF_F1: BoundOrFiller = BoundOrFiller::Bound(UserBounds { - l: Side::Some(1), - r: Side::Some(1), - }); - - const BOF_F2: BoundOrFiller = BoundOrFiller::Bound(UserBounds { - l: Side::Some(2), - r: Side::Some(2), - }); - - const BOF_F3: BoundOrFiller = BoundOrFiller::Bound(UserBounds { - l: Side::Some(3), - r: Side::Some(3), - }); - - const BOF_R2_3: BoundOrFiller = BoundOrFiller::Bound(UserBounds { - l: Side::Some(2), - r: Side::Some(3), - }); - - const BOF_NEG1: BoundOrFiller = BoundOrFiller::Bound(UserBounds { - l: Side::Some(-1), - r: Side::Some(-1), - }); - - const BOF_F1_TO_END: BoundOrFiller = BoundOrFiller::Bound(UserBounds { - l: Side::Some(1), - r: Side::Continue, - }); - #[test] fn fwd_cut_one_field() { let mut opt = make_lines_opt(); - opt.bounds = UserBoundsList(vec![BOF_F1]); + opt.bounds = UserBoundsList::from_str("1").unwrap(); let mut input = b"a\nb".as_slice(); let mut output = Vec::with_capacity(100); @@ -191,7 +163,7 @@ mod tests { #[test] fn fwd_cut_multiple_fields() { let mut opt = make_lines_opt(); - opt.bounds = UserBoundsList(vec![BOF_F1, BOF_F2]); + opt.bounds = UserBoundsList::from_str("1:2").unwrap(); let mut input = b"a\nb".as_slice(); let mut output = Vec::with_capacity(100); @@ -202,7 +174,7 @@ mod tests { #[test] fn fwd_support_ranges() { let mut opt = make_lines_opt(); - opt.bounds = UserBoundsList(vec![BOF_F1, BOF_R2_3]); + opt.bounds = UserBoundsList::from_str("1,2:3").unwrap(); let mut input = b"a\nb\nc".as_slice(); let mut output = Vec::with_capacity(100); @@ -213,7 +185,7 @@ mod tests { #[test] fn fwd_supports_no_join() { let mut opt = make_lines_opt(); - opt.bounds = UserBoundsList(vec![BOF_F1, BOF_F3]); + opt.bounds = UserBoundsList::from_str("1,3").unwrap(); opt.join = false; let mut input = b"a\nb\nc".as_slice(); @@ -225,7 +197,7 @@ mod tests { #[test] fn fwd_supports_no_right_bound() { let mut opt = make_lines_opt(); - opt.bounds = UserBoundsList(vec![BOF_F1_TO_END]); + opt.bounds = UserBoundsList::from_str("1:").unwrap(); let mut input = b"a\nb".as_slice(); let mut output = Vec::with_capacity(100); @@ -236,7 +208,7 @@ mod tests { #[test] fn fwd_handle_out_of_bounds() { let mut opt = make_lines_opt(); - opt.bounds = UserBoundsList(vec![BOF_F3]); + opt.bounds = UserBoundsList::from_str("3").unwrap(); opt.join = true; let mut input = b"a\nb".as_slice(); @@ -248,7 +220,7 @@ mod tests { #[test] fn fwd_ignore_last_empty() { let mut opt = make_lines_opt(); - opt.bounds = UserBoundsList(vec![BOF_F3]); + opt.bounds = UserBoundsList::from_str("3").unwrap(); let mut input1 = b"a\nb".as_slice(); let mut input2 = b"a\nb\n".as_slice(); @@ -266,7 +238,7 @@ mod tests { #[test] fn cut_lines_handle_negative_idx() { let mut opt = make_lines_opt(); - opt.bounds = UserBoundsList(vec![BOF_NEG1]); + opt.bounds = UserBoundsList::from_str("-1").unwrap(); let mut input = b"a\nb".as_slice(); let mut output = Vec::with_capacity(100); @@ -277,7 +249,7 @@ mod tests { #[test] fn cut_lines_ignore_last_empty_when_using_positive_idx() { let mut opt = make_lines_opt(); - opt.bounds = UserBoundsList(vec![BOF_F3]); + opt.bounds = UserBoundsList::from_str("3").unwrap(); let mut input1 = b"a\nb".as_slice(); let mut input2 = b"a\nb\n".as_slice(); @@ -295,7 +267,7 @@ mod tests { #[test] fn cut_lines_ignore_last_empty_when_using_negative_idx() { let mut opt = make_lines_opt(); - opt.bounds = UserBoundsList(vec![BOF_NEG1]); + opt.bounds = UserBoundsList::from_str("-1").unwrap(); let mut input1 = b"a\nb".as_slice(); let mut input2 = b"a\nb\n".as_slice(); @@ -313,7 +285,7 @@ mod tests { #[test] fn fwd_cut_zero_delimited() { let mut opt = make_lines_opt(); - opt.bounds = UserBoundsList(vec![BOF_F1]); + opt.bounds = UserBoundsList::from_str("1").unwrap(); opt.eol = EOL::Zero; opt.delimiter = String::from("\0"); @@ -326,7 +298,7 @@ mod tests { #[test] fn cut_lines_zero_delimited() { let mut opt = make_lines_opt(); - opt.bounds = UserBoundsList(vec![BOF_F1]); + opt.bounds = UserBoundsList::from_str("1").unwrap(); opt.eol = EOL::Zero; opt.delimiter = String::from("\0"); diff --git a/src/cut_str.rs b/src/cut_str.rs index 3867d4a..2072230 100644 --- a/src/cut_str.rs +++ b/src/cut_str.rs @@ -2,7 +2,7 @@ use anyhow::{bail, Result}; use std::io::{BufRead, Write}; use std::ops::Range; -use crate::bounds::{BoundOrFiller, BoundsType, Side, UserBounds, UserBoundsList}; +use crate::bounds::{BoundOrFiller, BoundsType, Side, UserBounds, UserBoundsList, UserBoundsTrait}; use crate::json::escape_json; use crate::options::{Opt, Trim}; use crate::read_utils::read_line_with_eol; @@ -313,7 +313,8 @@ pub fn cut_str( b, BoundOrFiller::Bound(UserBounds { l: x, - r: y + r: y, + is_last: _, }) if x != y || x == &Side::Continue ) }) { diff --git a/src/fast_lane.rs b/src/fast_lane.rs index db6b5c6..7f49d3b 100644 --- a/src/fast_lane.rs +++ b/src/fast_lane.rs @@ -1,12 +1,10 @@ -use crate::bounds::{BoundOrFiller, BoundsType, Side, UserBounds, UserBoundsList}; +use crate::bounds::{BoundOrFiller, BoundsType, Side, UserBounds, UserBoundsList, UserBoundsTrait}; use crate::options::{Opt, Trim, EOL}; -use anyhow::{bail, Result}; +use anyhow::Result; use bstr::ByteSlice; use std::convert::TryFrom; +use std::io::Write; use std::io::{self, BufRead}; -use std::ops::Deref; -use std::str::FromStr; -use std::{io::Write, ops::Range}; use bstr::io::BufReadExt; @@ -20,11 +18,12 @@ fn trim<'a>(buffer: &'a [u8], trim_kind: &Trim, delimiter: u8) -> &'a [u8] { } } +#[inline(always)] fn cut_str_fast_lane( initial_buffer: &[u8], opt: &FastOpt, stdout: &mut W, - fields: &mut Vec>, + fields: &mut Vec, last_interesting_field: Side, ) -> Result<()> { let mut buffer = initial_buffer; @@ -42,8 +41,6 @@ fn cut_str_fast_lane( let bounds = &opt.bounds; - let mut prev_field_start = 0; - let mut curr_field = 0; fields.clear(); @@ -51,10 +48,7 @@ fn cut_str_fast_lane( for i in memchr::memchr_iter(opt.delimiter, buffer) { curr_field += 1; - let (start, end) = (prev_field_start, i); // end exclusive - prev_field_start = i + 1; - - fields.push(Range { start, end }); + fields.push(i); if Side::Some(curr_field) == last_interesting_field { // We have no use for any other fields in this line @@ -67,39 +61,30 @@ fn cut_str_fast_lane( return Ok(()); } - // After the last loop ended, everything remaining is the field - // after the last delimiter (we want it), or "useless" fields after the - // last one that the user is interested in (and we can ignore them). if Side::Some(curr_field) != last_interesting_field { - fields.push(Range { - start: prev_field_start, - end: buffer.len(), - }); + // We reached the end of the line. Who knows, maybe + // the user is interested in this field too. + fields.push(buffer.len()); } let num_fields = fields.len(); match num_fields { - 1 if bounds.len() == 1 && fields[0].end == buffer.len() => { + 1 if bounds.len() == 1 && fields[0] == buffer.len() => { stdout.write_all(buffer)?; } _ => { - bounds - .iter() - .enumerate() - .try_for_each(|(bounds_idx, bof)| -> Result<()> { - let b = match bof { - BoundOrFiller::Filler(f) => { - stdout.write_all(f.as_bytes())?; - return Ok(()); - } - BoundOrFiller::Bound(b) => b, - }; - - let is_last = bounds_idx == bounds.len() - 1; - - output_parts(buffer, b, fields, stdout, is_last, opt) - })?; + bounds.iter().try_for_each(|bof| -> Result<()> { + match bof { + BoundOrFiller::Filler(f) => { + stdout.write_all(f.as_bytes())?; + } + BoundOrFiller::Bound(b) => { + output_parts(buffer, b, fields, stdout, opt)?; + } + }; + Ok(()) + })?; } } @@ -114,21 +99,25 @@ fn output_parts( // which parts to print b: &UserBounds, // where to find the parts inside `line` - fields: &[Range], + fields: &[usize], stdout: &mut W, - is_last: bool, opt: &FastOpt, ) -> Result<()> { let r = b.try_into_range(fields.len())?; - let idx_start = fields[r.start].start; - let idx_end = fields[r.end - 1].end; + let idx_start = if r.start == 0 { + 0 + } else { + fields[r.start - 1] + 1 + }; + let idx_end = fields[r.end - 1]; + let output = &line[idx_start..idx_end]; let field_to_print = output; stdout.write_all(field_to_print)?; - if opt.join && !(is_last) { + if opt.join && !b.is_last { stdout.write_all(&[opt.delimiter])?; } @@ -136,32 +125,19 @@ fn output_parts( } #[derive(Debug)] -pub struct FastOpt { +pub struct FastOpt<'a> { delimiter: u8, join: bool, eol: EOL, - bounds: ForwardBounds, + bounds: &'a UserBoundsList, only_delimited: bool, trim: Option, } -impl Default for FastOpt { - fn default() -> Self { - Self { - delimiter: b'\t', - join: false, - eol: EOL::Newline, - bounds: ForwardBounds::try_from(&UserBoundsList::from_str("1:").unwrap()).unwrap(), - only_delimited: false, - trim: None, - } - } -} - -impl TryFrom<&Opt> for FastOpt { +impl<'a> TryFrom<&'a Opt> for FastOpt<'a> { type Error = &'static str; - fn try_from(value: &Opt) -> Result { + fn try_from(value: &'a Opt) -> Result { if value.delimiter.as_bytes().len() != 1 { return Err("Delimiter must be 1 byte wide for FastOpt"); } @@ -179,77 +155,15 @@ impl TryFrom<&Opt> for FastOpt { ); } - if let Ok(forward_bounds) = ForwardBounds::try_from(&value.bounds) { - Ok(FastOpt { - delimiter: value.delimiter.as_bytes().first().unwrap().to_owned(), - join: value.join, - eol: value.eol, - bounds: forward_bounds, - only_delimited: value.only_delimited, - trim: value.trim, - }) - } else { - Err("Bounds cannot be converted to ForwardBounds") - } - } -} - -#[derive(Debug)] -struct ForwardBounds { - pub list: UserBoundsList, - // Optimization that we can use to stop searching for fields - // It's available only when every bound use positive indexes. - // When conditions do not apply, Side::Continue is used. - last_interesting_field: Side, -} - -impl TryFrom<&UserBoundsList> for ForwardBounds { - type Error = anyhow::Error; - - fn try_from(value: &UserBoundsList) -> Result { - if value.is_empty() { - bail!("Cannot create ForwardBounds from an empty UserBoundsList"); - } else { - let value: UserBoundsList = UserBoundsList(value.iter().cloned().collect()); - - let mut rightmost_bound: Option = None; - if value.is_sortable() { - value.iter().for_each(|bof| { - if let BoundOrFiller::Bound(b) = bof { - if rightmost_bound.is_none() || b.r > rightmost_bound.unwrap() { - rightmost_bound = Some(b.r); - } - } - }); - } - - Ok(ForwardBounds { - list: value, - last_interesting_field: rightmost_bound.unwrap_or(Side::Continue), - }) - } - } -} - -impl Deref for ForwardBounds { - type Target = UserBoundsList; - - fn deref(&self) -> &Self::Target { - &self.list - } -} - -impl ForwardBounds { - fn get_last_bound(&self) -> Side { - self.last_interesting_field - } -} - -impl FromStr for ForwardBounds { - type Err = anyhow::Error; - fn from_str(s: &str) -> Result { - let bounds_list = UserBoundsList::from_str(s)?; - ForwardBounds::try_from(&bounds_list) + let delimiter = value.delimiter.as_bytes().first().unwrap().to_owned(); + Ok(FastOpt { + delimiter, + join: value.join, + eol: value.eol, + bounds: &value.bounds, + only_delimited: value.only_delimited, + trim: value.trim, + }) } } @@ -258,9 +172,9 @@ pub fn read_and_cut_text_as_bytes( stdout: &mut W, opt: &FastOpt, ) -> Result<()> { - let mut fields: Vec> = Vec::with_capacity(16); + let mut fields: Vec = Vec::with_capacity(16); - let last_interesting_field = opt.bounds.get_last_bound(); + let last_interesting_field = opt.bounds.last_interesting_field; match opt.eol { EOL::Newline => stdin.for_byte_line(|line| { @@ -288,10 +202,17 @@ mod tests { use super::*; - fn make_fields_opt() -> FastOpt { + fn make_fields_opt(bounds_as_text: &str) -> FastOpt<'static> { + let boxed_bounds = Box::new(UserBoundsList::from_str(bounds_as_text).unwrap()); + let bounds: &'static mut UserBoundsList = Box::leak(boxed_bounds); + FastOpt { delimiter: b'-', - ..FastOpt::default() + join: false, + eol: EOL::Newline, + bounds, + only_delimited: false, + trim: None, } } @@ -300,7 +221,7 @@ mod tests { // read_and_cut_str is difficult to test, let's verify at least // that it reads the input and appears to call cut_str - let opt = make_fields_opt(); + let opt = make_fields_opt("1:"); let mut input = b"foo".as_slice(); let mut output = Vec::new(); read_and_cut_text_as_bytes(&mut input, &mut output, &opt).unwrap(); @@ -321,7 +242,7 @@ mod tests { ); } - fn make_cut_str_buffers() -> (Vec, Vec>) { + fn make_cut_str_buffers() -> (Vec, Vec) { let output = Vec::new(); let fields = Vec::new(); (output, fields) @@ -329,7 +250,7 @@ mod tests { #[test] fn cut_str_echo_non_delimited_strings() { - let opt = make_fields_opt(); + let opt = make_fields_opt("1:"); // non-empty line missing the delimiter let line = b"foo"; @@ -339,7 +260,7 @@ mod tests { &opt, &mut output, &mut fields, - opt.bounds.get_last_bound(), + opt.bounds.last_interesting_field, ) .unwrap(); assert_eq!(output, b"foo\n".as_slice()); @@ -352,7 +273,7 @@ mod tests { &opt, &mut output, &mut fields, - opt.bounds.get_last_bound(), + opt.bounds.last_interesting_field, ) .unwrap(); assert_eq!(output, b"\n".as_slice()); @@ -360,7 +281,7 @@ mod tests { #[test] fn cut_str_skip_non_delimited_strings_when_requested() { - let mut opt = make_fields_opt(); + let mut opt = make_fields_opt("1:"); opt.only_delimited = true; @@ -372,7 +293,7 @@ mod tests { &opt, &mut output, &mut fields, - opt.bounds.get_last_bound(), + opt.bounds.last_interesting_field, ) .unwrap(); assert_eq!(output, b"".as_slice()); @@ -385,7 +306,7 @@ mod tests { &opt, &mut output, &mut fields, - opt.bounds.get_last_bound(), + opt.bounds.last_interesting_field, ) .unwrap(); assert_eq!(output, b"".as_slice()); @@ -393,18 +314,17 @@ mod tests { #[test] fn cut_str_it_cut_a_field() { - let mut opt = make_fields_opt(); + let opt = make_fields_opt("1"); let (mut output, mut fields) = make_cut_str_buffers(); let line = b"a-b-c"; - opt.bounds = ForwardBounds::from_str("1").unwrap(); cut_str_fast_lane( line, &opt, &mut output, &mut fields, - opt.bounds.get_last_bound(), + opt.bounds.last_interesting_field, ) .unwrap(); assert_eq!(output, b"a\n".as_slice()); @@ -412,45 +332,44 @@ mod tests { #[test] fn cut_str_it_cut_with_negative_indices() { - let mut opt = make_fields_opt(); + // just one negative index + let opt = make_fields_opt("-1"); let line = b"a-b-c"; - // just one negative index - opt.bounds = ForwardBounds::from_str("-1").unwrap(); let (mut output, mut fields) = make_cut_str_buffers(); cut_str_fast_lane( line, &opt, &mut output, &mut fields, - opt.bounds.get_last_bound(), + opt.bounds.last_interesting_field, ) .unwrap(); assert_eq!(output, b"c\n".as_slice()); // multiple negative indices, in forward order - opt.bounds = ForwardBounds::from_str("-2,-1").unwrap(); + let opt = make_fields_opt("-2,-1"); let (mut output, mut fields) = make_cut_str_buffers(); cut_str_fast_lane( line, &opt, &mut output, &mut fields, - opt.bounds.get_last_bound(), + opt.bounds.last_interesting_field, ) .unwrap(); assert_eq!(output, b"bc\n".as_slice()); // multiple negative indices, in non-forward order - opt.bounds = ForwardBounds::from_str("-1,-2").unwrap(); + let opt = make_fields_opt("-1,-2"); let (mut output, mut fields) = make_cut_str_buffers(); cut_str_fast_lane( line, &opt, &mut output, &mut fields, - opt.bounds.get_last_bound(), + opt.bounds.last_interesting_field, ) .unwrap(); assert_eq!(output, b"cb\n".as_slice()); @@ -458,14 +377,14 @@ mod tests { // mix positive and negative indices // (this is particularly useful to verify that we don't screw // up optimizations on last field to check) - opt.bounds = ForwardBounds::from_str("-1,1").unwrap(); + let opt = make_fields_opt("-1,1"); let (mut output, mut fields) = make_cut_str_buffers(); cut_str_fast_lane( line, &opt, &mut output, &mut fields, - opt.bounds.get_last_bound(), + opt.bounds.last_interesting_field, ) .unwrap(); assert_eq!(output, b"ca\n".as_slice()); @@ -473,18 +392,17 @@ mod tests { #[test] fn cut_str_it_cut_consecutive_delimiters() { - let mut opt = make_fields_opt(); + let opt = make_fields_opt("1,3"); let (mut output, mut fields) = make_cut_str_buffers(); let line = b"a-b-c"; - opt.bounds = ForwardBounds::from_str("1,3").unwrap(); cut_str_fast_lane( line, &opt, &mut output, &mut fields, - opt.bounds.get_last_bound(), + opt.bounds.last_interesting_field, ) .unwrap(); assert_eq!(output, b"ac\n".as_slice()); @@ -492,19 +410,18 @@ mod tests { #[test] fn cut_str_it_supports_zero_terminated_lines() { - let mut opt = make_fields_opt(); + let mut opt = make_fields_opt("2"); let (mut output, mut fields) = make_cut_str_buffers(); opt.eol = EOL::Zero; let line = b"a-b-c"; - opt.bounds = ForwardBounds::from_str("2").unwrap(); cut_str_fast_lane( line, &opt, &mut output, &mut fields, - opt.bounds.get_last_bound(), + opt.bounds.last_interesting_field, ) .unwrap(); assert_eq!(output, b"b\0".as_slice()); @@ -512,11 +429,10 @@ mod tests { #[test] fn cut_str_it_join_fields() { - let mut opt = make_fields_opt(); + let mut opt = make_fields_opt("1,3"); let (mut output, mut fields) = make_cut_str_buffers(); let line = b"a-b-c"; - opt.bounds = ForwardBounds::from_str("1,3").unwrap(); opt.join = true; cut_str_fast_lane( @@ -524,7 +440,7 @@ mod tests { &opt, &mut output, &mut fields, - opt.bounds.get_last_bound(), + opt.bounds.last_interesting_field, ) .unwrap(); assert_eq!(output, b"a-c\n".as_slice()); @@ -532,31 +448,47 @@ mod tests { #[test] fn cut_str_it_format_fields() { - let mut opt = make_fields_opt(); + let opt = make_fields_opt("{2}"); let (mut output, mut fields) = make_cut_str_buffers(); let line = b"a-b-c"; - opt.bounds = ForwardBounds::from_str("{1} < {3} > {2}").unwrap(); cut_str_fast_lane( line, &opt, &mut output, &mut fields, - opt.bounds.get_last_bound(), + opt.bounds.last_interesting_field, ) .unwrap(); - assert_eq!(output, b"a < c > b\n".as_slice()); + assert_eq!(output.to_str_lossy(), b"b\n".as_slice().to_str_lossy()); + + let opt = make_fields_opt("{1} < {3} > {2}"); + let (mut output, mut fields) = make_cut_str_buffers(); + + let line = b"a-b-c"; + + cut_str_fast_lane( + line, + &opt, + &mut output, + &mut fields, + opt.bounds.last_interesting_field, + ) + .unwrap(); + assert_eq!( + output.to_str_lossy(), + b"a < c > b\n".as_slice().to_str_lossy() + ); } #[test] fn cut_str_it_trim_fields() { - let mut opt = make_fields_opt(); + let mut opt = make_fields_opt("1,3,-1"); let line = b"--a--b--c--"; // check Trim::Both opt.trim = Some(Trim::Both); - opt.bounds = ForwardBounds::from_str("1,3,-1").unwrap(); let (mut output, mut fields) = make_cut_str_buffers(); cut_str_fast_lane( @@ -564,14 +496,14 @@ mod tests { &opt, &mut output, &mut fields, - opt.bounds.get_last_bound(), + opt.bounds.last_interesting_field, ) .unwrap(); assert_eq!(output, b"abc\n".as_slice()); // check Trim::Left + let mut opt = make_fields_opt("1,3,-3"); opt.trim = Some(Trim::Left); - opt.bounds = ForwardBounds::from_str("1,3,-3").unwrap(); let (mut output, mut fields) = make_cut_str_buffers(); cut_str_fast_lane( @@ -579,14 +511,14 @@ mod tests { &opt, &mut output, &mut fields, - opt.bounds.get_last_bound(), + opt.bounds.last_interesting_field, ) .unwrap(); assert_eq!(output, b"abc\n".as_slice()); // check Trim::Right + let mut opt = make_fields_opt("3,5,-1"); opt.trim = Some(Trim::Right); - opt.bounds = ForwardBounds::from_str("3,5,-1").unwrap(); let (mut output, mut fields) = make_cut_str_buffers(); cut_str_fast_lane( @@ -594,7 +526,7 @@ mod tests { &opt, &mut output, &mut fields, - opt.bounds.get_last_bound(), + opt.bounds.last_interesting_field, ) .unwrap(); assert_eq!(output, b"abc\n".as_slice()); diff --git a/src/options.rs b/src/options.rs index d4eefbf..c93eeb0 100644 --- a/src/options.rs +++ b/src/options.rs @@ -1,4 +1,4 @@ -use crate::bounds::{BoundOrFiller, BoundsType, UserBounds, UserBoundsList}; +use crate::bounds::{BoundsType, UserBoundsList}; use anyhow::Result; use std::str::FromStr; @@ -54,7 +54,7 @@ impl Default for Opt { Opt { delimiter: String::from("-"), eol: EOL::Newline, - bounds: UserBoundsList(vec![BoundOrFiller::Bound(UserBounds::default())]), + bounds: UserBoundsList::from_str("1:").unwrap(), bounds_type: BoundsType::Fields, only_delimited: false, greedy_delimiter: false,