onefetch/info/langs/
mod.rs

1use language::{Language, LanguageType};
2use std::collections::HashMap;
3use std::path::Path;
4use strum::IntoEnumIterator;
5
6pub mod language;
7
8pub fn get_main_language(loc_by_language_opt: Option<&Vec<(Language, usize)>>) -> Option<Language> {
9    loc_by_language_opt.map(|loc_by_language| loc_by_language[0].0)
10}
11
12/// Returns a vector of tuples containing all the languages detected inside the repository.
13/// Each tuple is composed of the language and its corresponding loc (lines of code).
14/// The vector is sorted by loc in descending order.
15pub fn get_loc_by_language_sorted(
16    dir: &Path,
17    globs_to_exclude: &[String],
18    language_types: &[LanguageType],
19    include_hidden: bool,
20) -> Option<Vec<(Language, usize)>> {
21    let locs = get_locs(dir, globs_to_exclude, language_types, include_hidden);
22    let loc_by_language_opt = get_loc_by_language(&locs);
23    loc_by_language_opt.map(sort_by_loc)
24}
25
26fn sort_by_loc(map: HashMap<Language, usize>) -> Vec<(Language, usize)> {
27    let mut vec: Vec<(Language, usize)> = map.into_iter().collect();
28    vec.sort_by(|a, b| b.1.cmp(&a.1));
29    vec
30}
31
32fn get_loc_by_language(languages: &tokei::Languages) -> Option<HashMap<Language, usize>> {
33    let mut loc_by_language = HashMap::new();
34
35    for (language_name, language) in languages {
36        let loc = language::loc(language_name, language);
37
38        if loc == 0 {
39            continue;
40        }
41
42        loc_by_language.insert(Language::from(*language_name), loc);
43    }
44
45    let total_loc: usize = loc_by_language.values().sum();
46    if total_loc == 0 {
47        None
48    } else {
49        Some(loc_by_language)
50    }
51}
52
53pub fn get_total_loc(loc_by_language: &[(Language, usize)]) -> usize {
54    let total_loc: usize = loc_by_language.iter().map(|(_, v)| v).sum();
55    total_loc
56}
57
58fn get_locs(
59    dir: &Path,
60    globs_to_exclude: &[String],
61    language_types: &[LanguageType],
62    include_hidden: bool,
63) -> tokei::Languages {
64    let mut languages = tokei::Languages::new();
65    let filtered_languages = filter_languages_on_type(language_types);
66
67    let tokei_config = tokei::Config {
68        types: Some(filtered_languages),
69        hidden: Some(include_hidden),
70        ..tokei::Config::default()
71    };
72    let ignored: Vec<&str> = globs_to_exclude.iter().map(AsRef::as_ref).collect();
73    languages.get_statistics(&[&dir], &ignored, &tokei_config);
74    languages
75}
76
77fn filter_languages_on_type(types: &[LanguageType]) -> Vec<tokei::LanguageType> {
78    Language::iter()
79        .filter(|language| types.contains(&language.get_type()))
80        .map(std::convert::Into::into)
81        .collect()
82}
83
84#[cfg(test)]
85mod test {
86    use super::*;
87    use tokei;
88
89    #[test]
90    fn get_loc_by_language_counts_md_comments() {
91        let js = tokei::Language {
92            blanks: 25,
93            comments: 50,
94            code: 100,
95            ..Default::default()
96        };
97        let js_type = tokei::LanguageType::JavaScript;
98
99        let md = tokei::Language {
100            blanks: 50,
101            comments: 200,
102            code: 100,
103            ..Default::default()
104        };
105        let md_type = tokei::LanguageType::Markdown;
106
107        let mut languages = tokei::Languages::new();
108        languages.insert(js_type, js);
109        languages.insert(md_type, md);
110
111        let loc_by_language = get_loc_by_language(&languages).unwrap();
112
113        // NOTE: JS  with 100 lines of code, MD with 300 lines of code + comments
114        assert_eq!(loc_by_language[&Language::JavaScript], 100);
115        assert_eq!(loc_by_language[&Language::Markdown], 300);
116    }
117
118    #[test]
119    fn deeply_nested_total_loc() {
120        let mut bash_code_stats = tokei::CodeStats::new();
121        // NOTE: When inside Markdown, comments should be counted as code
122        bash_code_stats.code = 5;
123        bash_code_stats.blanks = 1;
124        bash_code_stats.comments = 2;
125
126        let mut md_code_stats = tokei::CodeStats::new();
127        md_code_stats.code = 10;
128        md_code_stats.blanks = 2;
129        md_code_stats.comments = 4;
130        md_code_stats
131            .blobs
132            .insert(tokei::LanguageType::Bash, bash_code_stats);
133        // NOTE: This may break if tokei ever does more than just assign `name` to a field
134        let mut md_report = tokei::Report::new("/tmp/file.ipynb".into());
135        md_report.stats = md_code_stats;
136
137        let mut jupyter_notebook = tokei::Language::default();
138        jupyter_notebook
139            .children
140            .insert(tokei::LanguageType::Markdown, vec![md_report]);
141
142        let mut languages = tokei::Languages::new();
143        languages.insert(tokei::LanguageType::Jupyter, jupyter_notebook);
144
145        let loc_by_language = get_loc_by_language(&languages).unwrap();
146
147        assert_eq!(loc_by_language[&Language::Jupyter], 21);
148    }
149
150    // https://github.com/o2sh/onefetch/issues/966
151    #[test]
152    fn get_loc_by_language_should_not_panic_when_children_language_is_not_supported() {
153        let mut stylus_code_stats = tokei::CodeStats::new();
154        stylus_code_stats.code = 10;
155        stylus_code_stats.blanks = 2;
156        stylus_code_stats.comments = 4;
157
158        let mut stylus_report = tokei::Report::new("/tmp/file.vue".into());
159        stylus_report.stats = stylus_code_stats;
160
161        let mut vue = tokei::Language {
162            blanks: 50,
163            comments: 200,
164            code: 100,
165            ..Default::default()
166        };
167
168        vue.children
169            .insert(tokei::LanguageType::Stylus, vec![stylus_report]);
170
171        let mut languages = tokei::Languages::new();
172        languages.insert(tokei::LanguageType::Vue, vue);
173
174        let loc_by_language = get_loc_by_language(&languages).unwrap();
175
176        assert_eq!(loc_by_language[&Language::Vue], 110);
177    }
178
179    #[test]
180    fn test_get_loc_by_language_sorted() {
181        let mut map = HashMap::new();
182        map.insert(Language::Ada, 300);
183        map.insert(Language::Java, 40);
184        map.insert(Language::Rust, 1200);
185        map.insert(Language::Go, 8);
186
187        let sorted_map = sort_by_loc(map);
188
189        let expected_order = vec![
190            (Language::Rust, 1200),
191            (Language::Ada, 300),
192            (Language::Java, 40),
193            (Language::Go, 8),
194        ];
195        let actual_order: Vec<_> = sorted_map.into_iter().collect();
196
197        assert_eq!(expected_order, actual_order);
198    }
199
200    #[test]
201    fn test_get_total_loc() {
202        let loc_by_language = [(Language::JavaScript, 100), (Language::Markdown, 300)];
203        assert_eq!(get_total_loc(&loc_by_language), 400);
204    }
205}