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 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
59fn 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}