pc_rs/
lib.rs

1use std::io::{BufRead, Read};
2
3use regex::Regex;
4
5/// Parse columns from the given `input` & `delimiter`, passing the resulting
6/// column into the provided closure. The closure can be used to process
7/// the column value:
8///
9/// ```
10/// // Accumulate the values
11/// use pc_rs::parse_columns;
12///
13/// # fn main() -> Result<(), String> {
14/// let input = "1 2 3 4\na b c d";
15/// let input = Box::new(std::io::BufReader::new(input.as_bytes()));
16/// let mut results = Vec::new();
17/// parse_columns(input, 1, " ", |col| results.push(col.to_owned())).unwrap();
18/// assert_eq!(&results, &["1", "a"]);
19/// # Ok(())
20/// # }
21/// ```
22///
23/// ```
24/// // Print values
25/// use pc_rs::parse_columns;
26///
27/// # fn main() -> Result<(), String> {
28/// let input = "1 2 3 4\na b c d";
29/// let input = Box::new(std::io::BufReader::new(input.as_bytes()));
30/// parse_columns(input, 1, " ", |col| println!("{col}")).unwrap();
31/// # Ok(())
32/// # }
33/// ```
34pub 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}