Skip to main content

linguist/
container.rs

1use std::{collections::HashMap, ffi::OsString, path::Path};
2
3use crate::resolver::{HeuristicRule, Language};
4
5/// A `Container` can be used to implement a storage that holds [`Language`] and [`HeuristicRule`] definitions.
6///
7/// ## Features
8/// When the `matcher` feature is enabled, the `Container` trait will also expose methods to retrieve [`HeuristicRule`] definitions.
9pub trait Container {
10    /// Returns a list of all [`Language`] definitions identified by its name.
11    fn get_language_by_name(&self, name: &str) -> Option<&Language>;
12    /// Returns a list of all [`Language`] definitions identified by the extension of the given file.
13    fn get_languages_by_extension(&self, file: impl AsRef<Path>) -> Option<Vec<&Language>>;
14    /// Returns a list of all [`Language`] definitions identified by the name of the given file.
15    fn get_languages_by_filename(&self, file: impl AsRef<Path>) -> Option<Vec<&Language>>;
16    /// Returns a list of all [`Language`] definitions identified by its interpreter.
17    fn get_languages_by_interpreter(&self, interpreter: &str) -> Option<Vec<&Language>>;
18    /// Returns a list of all [`HeuristicRule`] definitions identified by the extension of the given file.
19    #[cfg(feature = "matcher")]
20    fn get_heuristics_by_extension(&self, file: impl AsRef<Path>) -> Option<&Vec<HeuristicRule>>;
21}
22
23#[derive(Debug, Default)]
24pub struct InMemoryLanguageContainer {
25    languages: Vec<Language>,
26    heuristics: HashMap<OsString, Vec<HeuristicRule>>,
27}
28
29impl InMemoryLanguageContainer {
30    pub fn register_language(&mut self, lang: impl Into<Language>) {
31        self.languages.push(lang.into());
32    }
33
34    #[cfg(feature = "matcher")]
35    pub fn register_heuristic_rule(&mut self, rule: impl Into<HeuristicRule>) {
36        let rule = rule.into();
37
38        for ext in &rule.extensions {
39            if let Some(heuristic) = self.heuristics.get_mut(ext) {
40                if !heuristic.contains(&rule) {
41                    heuristic.push(rule.clone());
42                } else {
43                }
44            } else {
45                self.heuristics
46                    .insert(ext.to_os_string(), vec![rule.clone()]);
47            }
48        }
49    }
50}
51
52impl Container for InMemoryLanguageContainer {
53    fn get_language_by_name(&self, name: &str) -> Option<&Language> {
54        self.languages
55            .iter()
56            .find(|lang| lang.name.to_lowercase() == *name.to_lowercase())
57    }
58
59    fn get_languages_by_extension(&self, file: impl AsRef<Path>) -> Option<Vec<&Language>> {
60        let ext = match file.as_ref().extension() {
61            Some(ext) => ext,
62            _ => match file.as_ref().file_name() {
63                Some(name) => name,
64                _ => return None,
65            },
66        };
67
68        let candidates: Vec<&Language> = self
69            .languages
70            .iter()
71            .filter(|lang| lang.extensions.contains(&OsString::from(ext)))
72            .collect();
73
74        if !candidates.is_empty() {
75            Some(candidates)
76        } else {
77            None
78        }
79    }
80
81    fn get_languages_by_filename(&self, file: impl AsRef<Path>) -> Option<Vec<&Language>> {
82        let candidates: Vec<&Language> = self
83            .languages
84            .iter()
85            .filter(|lang| {
86                lang.filenames
87                    .contains(&file.as_ref().as_os_str().to_os_string())
88            })
89            .collect();
90
91        if !candidates.is_empty() {
92            Some(candidates)
93        } else {
94            None
95        }
96    }
97
98    #[cfg(feature = "matcher")]
99    fn get_heuristics_by_extension(&self, file: impl AsRef<Path>) -> Option<&Vec<HeuristicRule>> {
100        let ext = match file.as_ref().extension() {
101            Some(val) => val,
102            _ => return None,
103        };
104
105        let heuristics = self.heuristics.get(&ext.to_os_string());
106        heuristics
107    }
108
109    fn get_languages_by_interpreter(&self, interpreter: &str) -> Option<Vec<&Language>> {
110        let interpreters: Vec<&Language> = self
111            .languages
112            .iter()
113            .filter(|lang| lang.interpreters.contains(&interpreter.to_string()))
114            .collect();
115
116        if !interpreters.is_empty() {
117            Some(interpreters)
118        } else {
119            None
120        }
121    }
122}