1use std::io::{BufRead, Read};
2
3use regex::Regex;
4
5pub fn parse_columns<T, F>(input: T, column: usize, delimiter: &str, mut f: F) -> anyhow::Result<()>
35where
36 T: BufRead + Read,
37 F: FnMut(&str),
38{
39 let delimiter_re = Regex::new(&format!("{delimiter}+"))?;
40 for line in input.lines() {
41 let line = line?;
42 let col = parse_column(&line, column, &delimiter_re)?;
43 f(col);
44 }
45 Ok(())
46}
47
48fn parse_column<'a>(line: &'a str, column: usize, delimiter_re: &Regex) -> anyhow::Result<&'a str> {
49 match column {
50 0 => Ok(line),
51 c => {
52 let col = delimiter_re
53 .split(line)
54 .nth(c - 1)
55 .ok_or_else(|| anyhow::anyhow!("Column overflow"))?;
56 Ok(col)
57 }
58 }
59}
60
61#[cfg(test)]
62mod tests {
63 use super::*;
64
65 macro_rules! parse_col {
66 ($input:expr, $column:expr, $delim:expr) => {{
67 let input = $input;
68 let mut results = Vec::new();
69 parse_columns(
70 Box::new(std::io::BufReader::new(input.as_bytes())),
71 $column,
72 $delim,
73 |col| results.push(col.to_owned()),
74 )
75 .unwrap();
76 results
77 }};
78 }
79
80 #[test]
81 fn test_parse_columns() {
82 assert_eq!(parse_col!("1 2 3 4\na b c d", 1, " "), &["1", "a"]);
83 assert_eq!(parse_col!("1 2 3 4\na b c d", 2, " "), &["2", "b"]);
84
85 assert_eq!(parse_col!("1,2,3,4\na,b,c,d", 3, ","), &["3", "c"]);
86 }
87}