#![allow(dead_code)] mod re; mod text; /// Run an expression on an named value with a result type `Result`. /// Where `T` and `U` have *the same API surface* for the duration of the provided expression. /// /// # Example /// If there is a value `let mut value: Result`, where `T: Write` & `U: BufWrite`; /// the expression `value.flush()` is valid for both `T` and `U`. /// Therefore, it can be simplified to be called as so: `unwrap_either(mut value => value.flush())`. /// /// # Reference capture vs. `move` capture. /// Note that by default, the identified value is **moved** *into* the expression. /// The type of reference can be controlled by appending `ref`, `mut`, or `ref mut` to the ident. /// /// Identifier capture table: /// - **none** ~default~ - Capture by move, value is immutable in expression. /// - `mut` - Capture by move, value is mutable in expression. /// - `ref` - Capture by ref, value is immutable (`&value`) in expression. /// - `ref mut` - Capture by mutable ref, value is mutable (`&mut value`) in expression. (__NOTE__: `value` must be defined as mutable to take a mutable reference of it.) /// /// Essentially the same rules as any `match` branch pattern. macro_rules! unwrap_either { ($res:ident => $($rest:tt)+) => { match $res { Ok(ref mut $res) => $($rest)+, Err(ref mut $res) => $($rest)+, } }; (ref mut $res:ident => $($rest:tt)+) => { match $res { Ok(ref mut $res) => $($rest)+, Err(ref mut $res) => $($rest)+, } }; (ref $res:ident => $($rest:tt)+) => { match $res { Ok(ref $res) => $($rest)+, Err(ref $res) => $($rest)+, } }; (mut $res:ident => $($rest:tt)+) => { match $res { Ok(mut $res) => $($rest)+, Err(mut $res) => $($rest)+, } }; } use color_eyre::{ eyre::{ self, eyre, WrapErr as _, }, SectionExt as _, Help as _, }; fn initialise() -> eyre::Result<()> { color_eyre::install()?; Ok(()) } #[inline] fn print_groups<'a, S: ?Sized, G, T: 'a, I>(to: &mut S, g: G, groups: I) -> std::io::Result<()> where S: std::io::Write, G: IntoIterator> + Clone + Copy, // NOTE: Copy bound to ensure we're not accidentally doing deep clones of `g`. //G: std::ops::Index, G::Output: std::borrow::Borrow>, T: std::borrow::Borrow, I: IntoIterator/*, IntoIter: ExactSizeIterator*/>, { use std::borrow::Borrow; let mut first = true; for group in groups.into_iter() { let group = group.borrow(); // // Moved to into match group (skipping invalid groups.) // if !first { // write!(to, "\t")?; // } let print_delim = || first.then_some("").unwrap_or("\t"); // If it's not the first iteration, print `\t`. match g.into_iter().nth(*group) { Some(None) => write!(to, "{}", print_delim()), Some(Some(g)) => write!(to, "{}{}", print_delim(), g.borrow()), //TODO: What should be the behaviour of a non-existent group index here? (NOTE: This now corresponds to the previous `g.len() > group` check in caller.) // (NOTE: The original behaviour is to just ignore groups that are out of range entirely (i.e. no printing, no delimit char, no error,) maybe treat non-existent groups as non-matched groups and *just* print the delim char?) // (NOTE: Moved out of branch, see above ^) // None if !first => write!(to, "\t"), // XXX: Should this do what it does now...? Or should it `break` to prevent the checking for more groups...? Print a warning maybe...? None => { eprintln!("Warning: Invalid group index {}!", group); continue; // Do not set `first = false` if it was an invalid index. //Ok(()) }, }?; first = false; } // If `first == true`, no groups were printed, so we do not print the new-line. if !first { to.write_all(b"\n") } else { Ok(()) } } fn main() -> eyre::Result<()> { initialise().wrap_err("Fatal: Failed to install panic handle")?; //let cli = args::parse_cli();//.wrap_err("Error parsing command-line arguments")?; //eprintln!("{:#?}", cli); // return Ok(()); let args: re::FrozenVec = std::env::args().map(String::into_boxed_str).collect(); if args.len() < 4 { println!("Usage: {} ...", args[0]); println!("Pass `-' as `' to read lines from stdin"); std::process::exit(1) } else { let re = re::Regex::compile(&args[2])?; let text = &args[1]; let groups = &args[3..]; if groups.len() < 1 { eprintln!("Warning: No capture groups requested."); // NOTE: Unexpected branch... return Ok(()); } let groups = groups.iter().enumerate() .map(|(i, x)| x.parse() .with_section(|| format!("{:?}", groups).header("Groups specified were")) .with_section(|| x.clone().header("Specified capture group index was")) .with_section(move || i.header("Argument index in provided groups"))) .collect::, _>>() .wrap_err("Invalid group index specified")?; //TODO: XXX: How to handle multiple groups in `stdin_lines()` case? //let group = groups[0]; //args[3].parse().expect("Invalid group number."); use std::io::Write; let mut stdout = std::io::stdout(); let stdout = if &text[..] == "-" { let mut stdout = std::io::BufWriter::new(stdout.lock()); text::stdin_lines(|text| -> eyre::Result { match re.exec(&text)? { Some(g) /*if g.len() > group*/ => // NOTE: This check branch has now been moved into `print_groups()` print_groups(&mut stdout, &g, &groups)?, //println!("{}", &g[group]), _ => (), } Ok(true) })?; Some(stdout) } else { match re.exec(&text)? { Some(g) /*if g.len() > group*/ => print_groups(&mut stdout, &g[..], &groups)?,//println!("{}", &g.nth(group).unwrap().map(|x| x.as_ref()).unwrap_or("")), _ => (), } None }.ok_or_else(move || stdout); unwrap_either!(mut stdout => stdout.flush()).unwrap(); } Ok(()) }