1use glob::glob;
2use rand::seq::SliceRandom;
3use rand::thread_rng;
4use std::{env, fmt};
5use std::{
6 error::Error,
7 fs::File,
8 io::{BufRead, BufReader},
9};
10
11type LinesResult<T> = Result<T, LinesError>;
12
13pub enum Language {
14 Rust,
15 Java,
16}
17
18impl Language {
19 fn default_folder(&self) -> Option<String> {
20 let home = match env::var("HOME") {
21 Ok(h) => h,
22 Err(_) => return None,
23 };
24
25 match self {
26 Language::Rust => Some(String::from(&format!("{home}/.cargo/registry/src/**/*.rs"))),
27 Language::Java => None,
28 }
29 }
30
31 fn env_var_folder(&self) -> Option<String> {
32 match self {
33 Language::Rust => match env::var("RUST_LINES") {
34 Ok(folder) => return Some(format!("{folder}/**/*.rust")),
35 Err(_) => None,
36 },
37 Language::Java => match env::var("JAVA_LINES") {
38 Ok(folder) => return Some(format!("{folder}/**/*.java")),
39 Err(_) => None,
40 },
41 }
42 }
43
44 fn folder(&self) -> Option<String> {
45 if self.env_var_folder().is_some() {
46 return self.env_var_folder();
47 }
48 if self.default_folder().is_some() {
49 return self.default_folder();
50 }
51 None
52 }
53
54 fn get_paths(&self) -> LinesResult<Vec<String>> {
55 if let Some(folder) = &self.folder() {
56 if let Ok(paths) = glob(folder) {
57 return Ok(paths
58 .filter_map(Result::ok)
59 .map(|p| p.display().to_string())
60 .collect());
61 };
62 }
63 Err(LinesError(format!(
64 "Error getting file paths for {}.",
65 self
66 )))
67 }
68}
69
70impl fmt::Display for Language {
71 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
72 match self {
73 Language::Rust => write!(f, "Rust"),
74 Language::Java => write!(f, "Java"),
75 }
76 }
77}
78
79pub struct LineConfig {
80 pub language: Language,
81}
82
83#[derive(Debug)]
84pub struct LinesError(String);
85
86impl fmt::Display for LinesError {
87 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
88 write!(f, "There is an error: {}", self.0)
89 }
90}
91
92impl Error for LinesError {}
93
94pub fn get_random_line(config: &LineConfig) -> LinesResult<String> {
95 match File::open(get_random_file_path(config)?) {
96 Ok(file) => get_random_string(filter_code_lines(config, get_lines_from_file(file))),
97 Err(e) => Err(LinesError(e.to_string())),
98 }
99}
100
101fn get_lines_from_file(file: File) -> Vec<String> {
102 BufReader::new(file)
103 .lines()
104 .filter_map(Result::ok)
105 .collect()
106}
107
108fn get_random_file_path(config: &LineConfig) -> LinesResult<String> {
109 get_random_string(config.language.get_paths()?)
110}
111
112fn filter_code_lines(config: &LineConfig, lines: Vec<String>) -> Vec<String> {
113 match config.language {
114 Language::Rust => lines
115 .into_iter()
116 .filter(|l| !l.contains('/') && l.len() > 10)
117 .map(|l| l.trim().to_string())
118 .collect(),
119 Language::Java => lines
120 .into_iter()
121 .filter(|l| !l.contains('/') && l.len() > 10)
122 .map(|l| l.trim().to_string())
123 .collect(),
124 }
125}
126
127fn get_random_string(lines: Vec<String>) -> LinesResult<String> {
128 match lines.choose(&mut thread_rng()) {
129 Some(line) => Ok(line.to_string()),
130 None => Err(LinesError(String::from("Error getting random string."))),
131 }
132}
133
134#[cfg(test)]
135mod tests {
136 use super::*;
137
138 fn get_test_code_lines_vec() -> Vec<String> {
139 vec![
140 "///".to_string(),
141 "let thing".to_string(),
142 " let thing = do_this_long_thing(hello)".to_string(),
143 ]
144 }
145
146 #[test]
147 fn test_filter_code_lines() {
148 let config = LineConfig {
149 language: Language::Rust,
150 };
151 let result = filter_code_lines(&config, get_test_code_lines_vec());
152 assert_eq!(result.len(), 1);
153 assert_eq!(
154 result.get(0).unwrap(),
155 "let thing = do_this_long_thing(hello)"
156 );
157 }
158
159 #[test]
160 fn test_get_random_line_one_line() {
161 let result = get_random_string(vec![String::from("random")]);
162 assert_eq!(result.unwrap(), String::from("random"));
163 }
164
165 #[test]
166 fn test_get_random_line_no_lines() {
167 let result = get_random_string(vec![]);
168 assert_eq!(true, result.is_err());
169 }
170}