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