bytes_radar/core/
registry.rs

1use once_cell::sync::Lazy;
2use serde::{Deserialize, Serialize};
3use std::collections::HashMap;
4use std::fmt::{Display, Formatter};
5use std::path::Path;
6
7#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
8pub enum LanguageType {
9    Programming,
10    Markup,
11    Data,
12    Configuration,
13    Documentation,
14    Other,
15}
16
17impl Default for LanguageType {
18    fn default() -> Self {
19        LanguageType::Programming
20    }
21}
22
23impl Display for LanguageType {
24    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
25        match self {
26            LanguageType::Programming => write!(f, "Programming"),
27            LanguageType::Markup => write!(f, "Markup"),
28            LanguageType::Data => write!(f, "Data"),
29            LanguageType::Configuration => write!(f, "Configuration"),
30            LanguageType::Documentation => write!(f, "Documentation"),
31            LanguageType::Other => write!(f, "Other"),
32        }
33    }
34}
35
36#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
37pub enum LineCommentPosition {
38    Any,
39    Start,
40}
41
42impl Default for LineCommentPosition {
43    fn default() -> Self {
44        LineCommentPosition::Any
45    }
46}
47
48#[derive(Debug, Clone, Serialize, Deserialize)]
49pub struct LanguageDefinition {
50    #[serde(default)]
51    pub name: String,
52    #[serde(skip_serializing_if = "Option::is_none")]
53    pub display_name: Option<String>,
54    #[serde(default, skip_serializing_if = "Vec::is_empty")]
55    pub extensions: Vec<String>,
56    #[serde(default, skip_serializing_if = "Vec::is_empty")]
57    pub filenames: Vec<String>,
58    #[serde(default, skip_serializing_if = "Vec::is_empty")]
59    pub shebangs: Vec<String>,
60    #[serde(default, skip_serializing_if = "Vec::is_empty")]
61    pub env: Vec<String>,
62    #[serde(default, skip_serializing_if = "Vec::is_empty")]
63    pub mime_types: Vec<String>,
64    #[serde(default, skip_serializing_if = "Vec::is_empty", alias = "line_comment")]
65    pub line_comments: Vec<String>,
66    #[serde(default, skip_serializing_if = "Vec::is_empty")]
67    pub multi_line_comments: Vec<(String, String)>,
68    #[serde(default, skip_serializing_if = "Vec::is_empty")]
69    pub nested_comments: Vec<(String, String)>,
70    #[serde(default, skip_serializing_if = "Vec::is_empty")]
71    pub doc_quotes: Vec<(String, String)>,
72    #[serde(default, skip_serializing_if = "Vec::is_empty")]
73    pub quotes: Vec<(String, String)>,
74    #[serde(default, skip_serializing_if = "Vec::is_empty")]
75    pub verbatim_quotes: Vec<(String, String)>,
76    #[serde(default, skip_serializing_if = "Vec::is_empty")]
77    pub important_syntax: Vec<String>,
78    #[serde(default)]
79    pub language_type: LanguageType,
80    #[serde(default, skip_serializing_if = "is_false")]
81    pub is_literate: bool,
82    #[serde(default, skip_serializing_if = "is_false", alias = "nested")]
83    pub is_nested: bool,
84    #[serde(default, skip_serializing_if = "is_false")]
85    pub is_blank: bool,
86    #[serde(default = "default_true", skip_serializing_if = "is_true")]
87    pub case_sensitive: bool,
88    #[serde(default)]
89    pub line_comment_position: LineCommentPosition,
90}
91
92fn is_false(b: &bool) -> bool {
93    !b
94}
95
96fn is_true(b: &bool) -> bool {
97    *b
98}
99
100fn default_true() -> bool {
101    true
102}
103
104pub struct LanguageRegistry;
105
106impl LanguageRegistry {
107    pub fn get_language(name: &str) -> Option<&'static LanguageDefinition> {
108        LANGUAGE_MAP.get(name)
109    }
110
111    pub fn detect_by_extension(extension: &str) -> Option<&'static LanguageDefinition> {
112        let ext = extension.to_lowercase();
113        EXTENSION_MAP
114            .get(&ext)
115            .and_then(|name| LANGUAGE_MAP.get(name))
116    }
117
118    pub fn detect_by_filename(filename: &str) -> Option<&'static LanguageDefinition> {
119        let lower_filename = filename.to_lowercase();
120        FILENAME_MAP
121            .get(&lower_filename)
122            .and_then(|name| LANGUAGE_MAP.get(name))
123    }
124
125    pub fn detect_by_path<P: AsRef<Path>>(path: P) -> Option<&'static LanguageDefinition> {
126        let path = path.as_ref();
127
128        if let Some(filename) = path.file_name().and_then(|n| n.to_str()) {
129            if let Some(lang) = Self::detect_by_filename(filename) {
130                return Some(lang);
131            }
132        }
133
134        if let Some(extension) = path.extension().and_then(|e| e.to_str()) {
135            return Self::detect_by_extension(extension);
136        }
137
138        None
139    }
140
141    pub fn all_languages() -> impl Iterator<Item = &'static LanguageDefinition> {
142        LANGUAGE_MAP.values()
143    }
144
145    pub fn languages_by_type(
146        lang_type: LanguageType,
147    ) -> impl Iterator<Item = &'static LanguageDefinition> {
148        LANGUAGE_MAP
149            .values()
150            .filter(move |lang| lang.language_type == lang_type)
151    }
152}
153
154fn create_languages() -> HashMap<String, LanguageDefinition> {
155    const LANGUAGES_JSON: &str = include_str!("../languages.json");
156    let mut languages: HashMap<String, LanguageDefinition> =
157        serde_json::from_str(LANGUAGES_JSON).expect("Failed to parse languages.json");
158
159    for (key, lang_def) in languages.iter_mut() {
160        if lang_def.name.is_empty() {
161            lang_def.name = key.clone();
162        }
163    }
164
165    languages
166}
167
168fn create_extension_map() -> HashMap<String, String> {
169    let mut map = HashMap::new();
170    for (name, lang) in LANGUAGE_MAP.iter() {
171        for ext in &lang.extensions {
172            map.insert(ext.clone(), name.clone());
173        }
174    }
175    map
176}
177
178fn create_filename_map() -> HashMap<String, String> {
179    let mut map = HashMap::new();
180    for (name, lang) in LANGUAGE_MAP.iter() {
181        for filename in &lang.filenames {
182            map.insert(filename.to_lowercase(), name.clone());
183        }
184    }
185    map
186}
187
188static LANGUAGE_MAP: Lazy<HashMap<String, LanguageDefinition>> = Lazy::new(create_languages);
189static EXTENSION_MAP: Lazy<HashMap<String, String>> = Lazy::new(create_extension_map);
190static FILENAME_MAP: Lazy<HashMap<String, String>> = Lazy::new(create_filename_map);