summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRyan Geary <rtgnj42@gmail.com>2019-09-15 18:16:42 -0400
committerRyan Geary <rtgnj42@gmail.com>2019-09-17 23:40:30 -0400
commitfd66e3cfb7f935b00befd7b04609cef7464e6e67 (patch)
tree0ca0e2840222eb61a2b033ba1c13c5c56359dec2
parent6c889b3963c797da78918f35709c3a12d0931cb0 (diff)
Move most of the processing out of main
-rw-r--r--src/choice.rs163
-rw-r--r--src/main.rs156
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>,