diff options
author | Ryan Geary <rtgnj42@gmail.com> | 2019-09-15 18:16:42 -0400 |
---|---|---|
committer | Ryan Geary <rtgnj42@gmail.com> | 2019-09-17 23:40:30 -0400 |
commit | fd66e3cfb7f935b00befd7b04609cef7464e6e67 (patch) | |
tree | 0ca0e2840222eb61a2b033ba1c13c5c56359dec2 | |
parent | 6c889b3963c797da78918f35709c3a12d0931cb0 (diff) |
Move most of the processing out of main
-rw-r--r-- | src/choice.rs | 163 | ||||
-rw-r--r-- | src/main.rs | 156 |
2 files changed, 165 insertions, 154 deletions
diff --git a/src/choice.rs b/src/choice.rs new file mode 100644 index 0000000..0b642fc --- /dev/null +++ b/src/choice.rs @@ -0,0 +1,163 @@ +#[cfg(test)] +mod tests { + #[test] + fn exp() { + assert_eq!(2 + 2, 4); + } +} + +use regex::Regex; +use std::convert::TryInto; +use std::num::ParseIntError; +use std::path::PathBuf; +use std::process; +use structopt::StructOpt; + +#[derive(Debug, StructOpt)] +#[structopt(name = "choose", about = "`choose` sections from each line of files")] +pub struct Opt { + /// Specify field separator other than whitespace + #[structopt(short, long)] + pub field_separator: Option<String>, + + /// Use inclusive ranges + #[structopt(short = "n", long)] + pub inclusive: bool, + + /// Activate debug mode + #[structopt(short, long)] + pub debug: bool, + + /// Input file + #[structopt(short, long, parse(from_os_str))] + pub input: Option<PathBuf>, + + /// Fields to print. Either x, x:, :y, or x:y, where x and y are integers, colons indicate a + /// range, and an empty field on either side of the colon continues to the beginning or end of + /// the line. + #[structopt(required = true, min_values = 1, parse(try_from_str = Choice::parse_choice))] + pub choice: Vec<Choice>, +} + +pub type Range = (Option<u32>, Option<u32>); + +#[derive(Debug)] +pub enum Choice { + Field(u32), + FieldRange(Range), +} + +impl Choice { + pub fn print_choice(&self, line: &String, opt: &Opt) { + let re = Regex::new(match &opt.field_separator { + Some(s) => s, + None => "[[:space:]]", + }) + .unwrap_or_else(|e| { + eprintln!("Failed to compile regular expression: {}", e); + // Exit code of 1 means failed to compile field_separator regex + process::exit(1); + }); + + let words = re + .split(line) + .into_iter() + .filter(|s| !s.is_empty()) + .enumerate(); + + match self { + Choice::Field(i) => { + print!( + "{} ", + words + .filter(|x| x.0 == *i as usize) + .map(|x| x.1) + .collect::<String>() + ); + } + Choice::FieldRange(r) => match r { + (None, None) => print!("{}", words.map(|x| x.1).collect::<String>()), + (Some(start), None) => print!( + "{} ", + words + .filter(|x| x.0 >= (*start).try_into().unwrap()) + .map(|x| x.1) + .collect::<Vec<&str>>() + .join(" ") + ), + (None, Some(end)) => { + let e: usize = if opt.inclusive { + (end + 1).try_into().unwrap() + } else { + (*end).try_into().unwrap() + }; + print!( + "{} ", + words + .filter(|x| x.0 < e) + .map(|x| x.1) + .collect::<Vec<&str>>() + .join(" ") + ) + } + (Some(start), Some(end)) => { + let e: usize = if opt.inclusive { + (end + 1).try_into().unwrap() + } else { + (*end).try_into().unwrap() + }; + print!( + "{} ", + words + .filter(|x| x.0 < e && x.0 >= (*start).try_into().unwrap()) + .map(|x| x.1) + .collect::<Vec<&str>>() + .join(" ") + ) + } + }, + }; + } + + pub fn parse_choice(src: &str) -> Result<Choice, ParseIntError> { + let re = Regex::new(r"^(\d*):(\d*)$").unwrap(); + + let cap = match re.captures_iter(src).next() { + Some(v) => v, + None => match src.parse() { + Ok(x) => return Ok(Choice::Field(x)), + Err(_) => { + eprintln!("failed to parse choice argument: {}", src); + // Exit code of 2 means failed to parse choice argument + process::exit(2); + } + }, + }; + + let start = if cap[1].is_empty() { + None + } else { + match cap[1].parse() { + Ok(x) => Some(x), + Err(_) => { + eprintln!("failed to parse range start: {}", &cap[1]); + process::exit(2); + } + } + }; + + let end = if cap[2].is_empty() { + None + } else { + match cap[2].parse() { + Ok(x) => Some(x), + Err(_) => { + eprintln!("failed to parse range end: {}", &cap[2]); + process::exit(2); + } + } + }; + + return Ok(Choice::FieldRange((start, end))); + } +} diff --git a/src/main.rs b/src/main.rs index 4d2e2bb..de1eb0f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,163 +1,11 @@ -use regex::Regex; -use std::convert::TryInto; use std::fs::File; use std::io::{self, BufRead, BufReader, Read}; -use std::num::ParseIntError; -use std::path::PathBuf; -use std::process; use structopt::StructOpt; -type Range = (Option<u32>, Option<u32>); - -#[derive(Debug)] -enum Choice { - Field(u32), - FieldRange(Range), -} - -impl Choice { - fn print_choice(&self, line: &String, opt: &Opt) { - let re = Regex::new(match &opt.field_separator { - Some(s) => s, - None => "[[:space:]]", - }) - .unwrap_or_else(|e| { - eprintln!("Failed to compile regular expression: {}", e); - // Exit code of 1 means failed to compile field_separator regex - process::exit(1); - }); - - let words = re - .split(line) - .into_iter() - .filter(|s| !s.is_empty()) - .enumerate(); - - match self { - Choice::Field(i) => { - print!( - "{} ", - words - .filter(|x| x.0 == *i as usize) - .map(|x| x.1) - .collect::<String>() - ); - } - Choice::FieldRange(r) => match r { - (None, None) => print!("{}", words.map(|x| x.1).collect::<String>()), - (Some(start), None) => print!( - "{} ", - words - .filter(|x| x.0 >= (*start).try_into().unwrap()) - .map(|x| x.1) - .collect::<Vec<&str>>() - .join(" ") - ), - (None, Some(end)) => { - let e: usize = if opt.inclusive { - (end + 1).try_into().unwrap() - } else { - (*end).try_into().unwrap() - }; - print!( - "{} ", - words - .filter(|x| x.0 < e) - .map(|x| x.1) - .collect::<Vec<&str>>() - .join(" ") - ) - } - (Some(start), Some(end)) => { - let e: usize = if opt.inclusive { - (end + 1).try_into().unwrap() - } else { - (*end).try_into().unwrap() - }; - print!( - "{} ", - words - .filter(|x| x.0 < e && x.0 >= (*start).try_into().unwrap()) - .map(|x| x.1) - .collect::<Vec<&str>>() - .join(" ") - ) - } - }, - }; - } - - fn parse_choice(src: &str) -> Result<Choice, ParseIntError> { - let re = Regex::new(r"^(\d*):(\d*)$").unwrap(); - - let cap = match re.captures_iter(src).next() { - Some(v) => v, - None => match src.parse() { - Ok(x) => return Ok(Choice::Field(x)), - Err(_) => { - eprintln!("failed to parse choice argument: {}", src); - // Exit code of 2 means failed to parse choice argument - process::exit(2); - } - }, - }; - - let start = if cap[1].is_empty() { - None - } else { - match cap[1].parse() { - Ok(x) => Some(x), - Err(_) => { - eprintln!("failed to parse range start: {}", &cap[1]); - process::exit(2); - } - } - }; - - let end = if cap[2].is_empty() { - None - } else { - match cap[2].parse() { - Ok(x) => Some(x), - Err(_) => { - eprintln!("failed to parse range end: {}", &cap[2]); - process::exit(2); - } - } - }; - - return Ok(Choice::FieldRange((start, end))); - } -} - -#[derive(Debug, StructOpt)] -#[structopt(name = "choose", about = "`choose` sections from each line of files")] -struct Opt { - /// Specify field separator other than whitespace - #[structopt(short, long)] - field_separator: Option<String>, - - /// Use inclusive ranges - #[structopt(short = "n", long)] - inclusive: bool, - - /// Activate debug mode - #[structopt(short, long)] - debug: bool, - - /// Input file - #[structopt(short, long, parse(from_os_str))] - input: Option<PathBuf>, - - /// Fields to print. Either x, x:, :y, or x:y, where x and y are integers, colons indicate a - /// range, and an empty field on either side of the colon continues to the beginning or end of - /// the line. - #[structopt(required = true, min_values = 1, parse(try_from_str = Choice::parse_choice))] - choice: Vec<Choice>, -} +mod choice; fn main() { - let opt = Opt::from_args(); + let opt = choice::Opt::from_args(); let read = match &opt.input { Some(f) => Box::new(File::open(f).expect("Could not open file")) as Box<dyn Read>, |