|
| 1 | +pub mod cache; |
| 2 | +pub mod loader; |
| 3 | +pub mod parser; |
1 | 4 | use napi::{ |
2 | 5 | bindgen_prelude::{JsObjectValue, Object}, |
3 | 6 | Env, Error, Result, |
4 | 7 | }; |
5 | 8 | use napi_derive::napi; |
6 | | -use once_cell::sync::Lazy; |
7 | | -use rayon::prelude::*; |
| 9 | +use std::fs; |
| 10 | +use std::path::Path; |
8 | 11 |
|
9 | | -use std::{collections::HashMap, fs, path::Path, sync::RwLock}; |
10 | | - |
11 | | -type LangCache = HashMap<String, HashMap<String, String>>; |
12 | | -static LANG_CACHE: Lazy<RwLock<Option<LangCache>>> = Lazy::new(|| RwLock::new(None)); |
13 | | - |
14 | | -#[napi] |
15 | | -pub fn load_lang(env: &Env, path: String) -> Result<Object<'_>> { |
16 | | - let path = Path::new(&path); |
17 | | - if !path.is_file() { |
18 | | - return Err(Error::from_reason(format!( |
19 | | - "Path '{}' is not a file", |
20 | | - path.display() |
21 | | - ))); |
22 | | - } |
23 | | - |
24 | | - let data = fs::read_to_string(path) |
25 | | - .map_err(|e| Error::from_reason(format!("Cannot read file '{}': {}", path.display(), e)))?; |
26 | | - let map = parse_lang_data(&data); |
27 | | - let mut js = Object::new(&env)?; |
28 | | - for (k, v) in map { |
29 | | - js.set_named_property(&k, env.create_string(&v)?)?; |
30 | | - } |
31 | | - Ok(js) |
32 | | -} |
33 | | - |
34 | | -fn parse_lang_data(data: &str) -> HashMap<String, String> { |
35 | | - let mut map = HashMap::new(); |
36 | | - let mut ckey: Option<String> = None; |
37 | | - let mut cval = String::new(); |
38 | | - let mut mtl = false; |
39 | | - let mut is_array = false; |
40 | | - |
41 | | - for line in data.lines() { |
42 | | - let line = line.trim_end(); |
43 | | - if line.is_empty() || line.starts_with('#') { |
44 | | - continue; |
45 | | - } |
46 | | - |
47 | | - if mtl { |
48 | | - cval.push_str(line); |
49 | | - cval.push('\n'); |
50 | | - if (is_array && line.trim().ends_with(']')) |
51 | | - || (!is_array && line.trim().ends_with('"') && !line.trim().ends_with("\\\"")) |
52 | | - { |
53 | | - mtl = false; |
54 | | - if let Some(key) = ckey.take() { |
55 | | - map.insert(key, cval.trim().to_string()); |
56 | | - } |
57 | | - cval.clear(); |
58 | | - is_array = false; |
59 | | - } |
60 | | - continue; |
61 | | - } |
62 | | - |
63 | | - if let Some(pos) = line.find('=') { |
64 | | - let key = line[..pos].trim().to_string(); |
65 | | - let value = line[pos + 1..].trim(); |
66 | | - |
67 | | - if key.is_empty() { |
68 | | - continue; |
69 | | - } |
70 | | - |
71 | | - if value.starts_with('[') { |
72 | | - if !value.ends_with(']') { |
73 | | - mtl = true; |
74 | | - is_array = true; |
75 | | - ckey = Some(key); |
76 | | - cval = value.to_string(); |
77 | | - cval.push('\n'); |
78 | | - } else { |
79 | | - map.insert(key, value.to_string()); |
80 | | - } |
81 | | - } else if value.starts_with('"') { |
82 | | - if !value.ends_with('"') || value.ends_with("\\\"") { |
83 | | - mtl = true; |
84 | | - ckey = Some(key); |
85 | | - cval = value.to_string(); |
86 | | - cval.push('\n'); |
87 | | - } else { |
88 | | - map.insert(key, value.trim_matches('"').to_string()); |
89 | | - } |
90 | | - } else { |
91 | | - map.insert(key, value.to_string()); |
92 | | - } |
93 | | - } |
94 | | - } |
95 | | - |
96 | | - map |
97 | | -} |
98 | | - |
99 | | -pub fn validate_path_is_dir(dir: &str) -> Result<&Path> { |
100 | | - let dirpath = Path::new(dir); |
101 | | - if !dirpath.is_dir() { |
102 | | - return Err(Error::from_reason(format!( |
103 | | - "Path '{}' is not a directory", |
104 | | - dir |
105 | | - ))); |
106 | | - } |
107 | | - Ok(dirpath) |
108 | | -} |
109 | | - |
110 | | -pub fn validate_files(files: &Path) -> Result<Vec<std::path::PathBuf>> { |
111 | | - let fl: Vec<_> = fs::read_dir(files) |
112 | | - .map_err(|e| Error::from_reason(e.to_string()))? |
113 | | - .filter_map(|entry| { |
114 | | - let entry = entry.ok()?; |
115 | | - let path = entry.path(); |
116 | | - if path.extension()? == "lang" { |
117 | | - Some(path) |
118 | | - } else { |
119 | | - None |
120 | | - } |
121 | | - }) |
122 | | - .collect(); |
123 | | - Ok(fl) |
124 | | -} |
125 | | - |
126 | | -fn load_lang_dsk(dir: &str) -> Result<LangCache> { |
127 | | - let dirpath = validate_path_is_dir(dir)?; |
128 | | - let files = validate_files(dirpath)?; |
129 | | - let results: LangCache = files |
130 | | - .par_iter() |
131 | | - .filter_map(|path| { |
132 | | - let name = path.file_stem()?.to_string_lossy().to_string(); |
133 | | - let data = fs::read_to_string(path).ok()?; |
134 | | - Some((name, parse_lang_data(&data))) |
135 | | - }) |
136 | | - .collect(); |
137 | | - Ok(results) |
138 | | -} |
| 12 | +use crate::loader::{load_lang_dir, LangCache}; |
139 | 13 |
|
140 | 14 | #[napi] |
141 | | -pub fn load_langs(env: &Env, dir: String) -> Result<Object<'_>> { |
142 | | - let results = load_lang_dsk(&dir)?; |
143 | | - to_js(env, &results) |
| 15 | +pub fn load_langs<'a>(env: &'a Env, dir: String) -> Result<Object<'a>> { |
| 16 | + let langs = load_lang_dir(Path::new(&dir))?; |
| 17 | + to_js(env, &langs) |
144 | 18 | } |
145 | 19 |
|
146 | 20 | #[napi] |
147 | | -pub fn load_chdlang(env: &Env, dir: String) -> Result<Object<'_>> { |
148 | | - { |
149 | | - let cache = LANG_CACHE.read().unwrap(); |
150 | | - if let Some(cached) = &*cache { |
151 | | - return to_js(env, cached); |
152 | | - } |
| 21 | +pub fn load_cached_langs<'a>(env: &'a Env, dir: String) -> Result<Object<'a>> { |
| 22 | + if let Some(cached) = cache::get() { |
| 23 | + return to_js(env, cached); |
153 | 24 | } |
154 | 25 |
|
155 | | - let langs = load_lang_dsk(&dir)?; |
156 | | - { |
157 | | - let mut cache = LANG_CACHE.write().unwrap(); |
158 | | - *cache = Some(langs.clone()); |
159 | | - } |
160 | | - |
161 | | - to_js(env, &langs) |
| 26 | + let langs = load_lang_dir(Path::new(&dir))?; |
| 27 | + cache::set(langs); |
| 28 | + to_js(env, cache::get().unwrap()) |
162 | 29 | } |
163 | 30 |
|
164 | 31 | fn to_js<'a>(env: &'a Env, langs: &LangCache) -> Result<Object<'a>> { |
@@ -208,7 +75,7 @@ pub fn generate_typescript_defs( |
208 | 75 | output: String, |
209 | 76 | gen_placeholder: Option<bool>, |
210 | 77 | ) -> Result<()> { |
211 | | - let langs = load_lang_dsk(&dir)?; |
| 78 | + let langs = load_lang_dir(Path::new(&dir))?; |
212 | 79 | let mut defs = String::new(); |
213 | 80 | defs.push_str("// THIS FILE WAS GENERATED BY SSL\n"); |
214 | 81 | defs.push_str("// DO NOT EDIT MANUALLY OR ELSE IT WILL BE OVERWRITTEN\n\n"); |
@@ -271,9 +138,3 @@ pub fn generate_typescript_defs( |
271 | 138 | } |
272 | 139 | Ok(()) |
273 | 140 | } |
274 | | - |
275 | | -#[napi] |
276 | | -pub fn clear_lang_cache() { |
277 | | - let mut cache = LANG_CACHE.write().unwrap(); |
278 | | - *cache = None; |
279 | | -} |
0 commit comments