axe_cli/
stdin.rs

1use std::{
2    io::{self, BufRead, BufReader},
3    path::PathBuf,
4};
5
6use clap::error::Result;
7
8use crate::cli::{Cli, EntriesOptions};
9
10pub fn read_entries(cli: &Cli) -> Vec<String> {
11    let stdin_lines = read_input_lines(&cli.args_file);
12    split_input_lines_into_entries(stdin_lines, &cli.entries, &cli.args_separator)
13}
14
15fn read_input_lines(args_file: &Option<PathBuf>) -> Vec<String> {
16    let reader: Box<dyn BufRead> = match args_file {
17        Some(path) => {
18            //FIXME: handle error
19            let file = std::fs::File::open(path).expect("Could not open file");
20            Box::new(BufReader::new(file))
21        }
22        None => Box::new(io::stdin().lock()),
23    };
24    reader
25        .lines()
26        .collect::<Result<Vec<String>, io::Error>>()
27        .unwrap_or_else(|_| {
28            panic!(
29                "Could not read args from {}",
30                args_file
31                    .as_ref()
32                    .and_then(|p| p.to_str())
33                    .unwrap_or("stdin")
34            )
35        })
36}
37
38fn split_input_lines_into_entries(
39    stdin_lines: Vec<String>,
40    entries: &EntriesOptions,
41    args_separator: &str,
42) -> Vec<String> {
43    match (
44        entries.single_entry,
45        entries.entry_size,
46        entries.entries_separator.as_str(),
47    ) {
48        (true, _, _) => vec![stdin_lines.join(args_separator)],
49        (_, size, _) if size > 0 => split_by_size(stdin_lines, size, args_separator),
50        (_, _, "\n") => stdin_lines,
51        (_, _, entry_sep) => stdin_lines
52            .join("")
53            .split(&entry_sep)
54            .map(|l| l.to_owned())
55            .collect(),
56    }
57}
58
59//TODO: This method may cause probvlems for large inputs
60fn split_by_size(stdin_lines: Vec<String>, size: usize, sep: &str) -> Vec<String> {
61    let all_args: Vec<String> = stdin_lines
62        .into_iter()
63        .flat_map(|l| l.split(sep).map(|s| s.to_string()).collect::<Vec<String>>())
64        .collect();
65    all_args.chunks(size).map(|c| c.join(sep)).collect()
66}
67
68#[cfg(test)]
69mod tests {
70
71    use super::*;
72
73    #[test]
74    fn should_parse_stdio_lines_as_input_entries_for_new_line_separator() {
75        let stdin_lines = vec!["a b c".to_string(), "d e f".to_string()];
76        let entries_options = EntriesOptions {
77            single_entry: false,
78            entries_separator: "\n".to_string(),
79            entry_size: 0,
80        };
81        let expected = stdin_lines.clone();
82        let actual = split_input_lines_into_entries(stdin_lines, &entries_options, " ");
83        assert_eq!(expected, actual);
84    }
85
86    #[test]
87    fn should_parse_stdio_lines_as_input_entries_for_colon_separator() {
88        let entries_options = EntriesOptions {
89            single_entry: false,
90            entries_separator: ";".to_string(),
91            entry_size: 0,
92        };
93        let stdin_lines = vec!["a b c;d e f;".to_string(), "g h i;j k l".to_string()];
94        let expected = vec![
95            "a b c".to_string(),
96            "d e f".to_string(),
97            "g h i".to_string(),
98            "j k l".to_string(),
99        ];
100        let actual = split_input_lines_into_entries(stdin_lines.clone(), &entries_options, " ");
101        assert_eq!(expected, actual);
102    }
103
104    #[test]
105    fn should_parse_stdio_lines_as_single_entry() {
106        let entries_options = EntriesOptions {
107            single_entry: true,
108            entries_separator: "\n".to_string(),
109            entry_size: 0,
110        };
111        let stdin_lines = vec!["a,b,c".to_string(), "d,e,f".to_string()];
112        let expected = vec!["a,b,c,d,e,f".to_string()];
113        let actual = split_input_lines_into_entries(stdin_lines, &entries_options, ",");
114        assert_eq!(expected, actual);
115    }
116
117    #[test]
118    fn should_parse_stdio_lines_as_entries_with_size() {
119        let entries_options = EntriesOptions {
120            single_entry: false,
121            entries_separator: "\n".to_string(),
122            entry_size: 2,
123        };
124        let stdin_lines = vec!["a;b;c".to_string(), "d;e;f;g".to_string()];
125        let expected = vec![
126            "a;b".to_string(),
127            "c;d".to_string(),
128            "e;f".to_string(),
129            "g".to_string(),
130        ];
131        let actual = split_input_lines_into_entries(stdin_lines, &entries_options, ";");
132        assert_eq!(expected, actual);
133    }
134}