code_lines/
lib.rs

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}