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);