lavendeux_parser/
extensions.rs

1use super::value::Value;
2use super::errors::*;
3use js_sandbox::{Script, AnyError};
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6use std::fs;
7
8/// Holds a set of registered extensions
9#[derive(Deserialize, Serialize, Clone)]
10pub struct ExtensionTable(HashMap<String, Extension>);
11impl ExtensionTable {
12    /// Create a new empty table
13    pub fn new() -> Self {
14        Self(HashMap::new())
15    }
16
17    /// Add an extension
18    /// 
19    /// # Arguments
20    /// * `filename` - File name
21    /// * `extension` - Extension to add
22    pub fn add(&mut self, filename: &str, extension: Extension) {
23        self.0.insert(filename.to_string(), extension);
24    }
25
26    /// Load an extension from a filename
27    /// 
28    /// # Arguments
29    /// * `filename` - File name
30    pub fn load(&mut self, filename: &str) -> Result<Extension, ParserError> {
31        let e = Extension::new(filename)?;
32        self.0.insert(filename.to_string(), e.clone());
33        Ok(e)
34    }
35
36    /// Attempt to load all extensions in a directory
37    pub fn load_all(&mut self, path: &str) -> Result<Vec<Extension>, ParserError> {
38        let e = Extension::load_all(path)?;
39        for extension in &e {
40            self.0.insert(extension.filename().to_string(), extension.clone());
41        }
42        Ok(e)
43    }
44
45    /// Delete an extension
46    pub fn remove(&mut self, filename: &str) {
47        self.0.remove(filename);
48    }
49
50    /// Returns the full list of extensions available
51    pub fn all(&self) -> Vec<Extension> {
52        Vec::from_iter(self.0.values().cloned())
53    }
54
55    /// Determine if a function exists in the extension
56    /// 
57    /// # Arguments
58    /// * `name` - Function name
59    pub fn has_function(&self, name: &str) -> bool {
60        for extension in self.all() {
61            if extension.has_function(name) {
62                return true;
63            }
64        }
65        false
66    }
67
68    /// Try to call a function in the loaded extensions
69    pub fn call_function(&self, name: &str, args: &[Value]) -> Result<Value, ParserError> {
70        for mut extension in self.all() {
71            if extension.has_function(name) {
72                return extension.call_function(name, args);
73            }
74        }
75        Err(ParserError::FunctionName(FunctionNameError::new(name)))
76    }
77
78    /// Determine if a decorator exists in the extension
79    /// 
80    /// # Arguments
81    /// * `name` - Decorator name
82    pub fn has_decorator(&self, name: &str) -> bool {
83        for extension in self.all() {
84            if extension.has_decorator(name) {
85                return true;
86            }
87        }
88        false
89    }
90
91    /// Try to call a decorator in the loaded extensions
92    pub fn call_decorator(&self, name: &str, arg: &Value) -> Result<String, ParserError> {
93        for mut extension in self.all() {
94            if extension.has_decorator(name) {
95                return extension.call_decorator(name, arg);
96            }
97        }
98        Err(ParserError::FunctionName(FunctionNameError::new(&format!("@{}", name))))
99    }
100}
101impl Default for ExtensionTable {
102    fn default() -> Self {
103        Self::new()
104    }
105}
106
107fn default_name() -> String { "Unnamed Extension".to_string() }
108fn default_author() -> String { "Anonymous".to_string() }
109fn default_version() -> String { "0.0.0".to_string() }
110
111/// Represents a single loaded extension. It describes the functions and decorators it adds,
112/// as well as metadata about the extension and it's author.
113/// 
114/// Add this to a ParserState to use it in expressions, or call the extension directly with
115/// call_function / call_decorator
116#[derive(Deserialize, Serialize, Clone, Debug, Eq, PartialEq)]
117pub struct Extension {
118    #[serde(default)]
119    filename: String,
120
121    #[serde(default = "default_name")]
122    name: String,
123
124    #[serde(default = "default_author")]
125    author: String,
126
127    #[serde(default = "default_version")]
128    version: String,
129    
130    #[serde(default)]
131    contents: String,
132    
133    #[serde(default)]
134    functions: HashMap<String, String>,
135    
136    #[serde(default)]
137    decorators: HashMap<String, String>
138}
139
140impl std::fmt::Display for Extension {
141    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
142        write!(f, "{} v{}, by {}", self.name, self.version, self.author)
143    }
144}
145
146unsafe impl Send for Extension {}
147impl Extension {
148    /// Load an extension from a filename
149    /// 
150    /// # Arguments
151    /// * `filename` - Source filename
152    pub fn new(filename: &str) -> Result<Extension, std::io::Error> {
153        match fs::read_to_string(filename) {
154            Ok(s) => {
155                match script_from_string(filename, &s) {
156                    Ok(v) => Ok(v),
157                    Err(e) => Err(std::io::Error::new(std::io::ErrorKind::InvalidData, e.to_string()))
158                }
159            },
160            Err(e) => Err(e)
161        }
162    }
163
164    /// Create a new dummy extension that cannot be called or used
165    /// 
166    /// # Arguments
167    /// * `name` - Extension name
168    /// * `author` - Extension author
169    /// * `author` - Extension author
170    /// * `version` - Extension version
171    /// * `functions` - Extension functions
172    /// * `decorators` - Extension decorators
173    pub fn new_stub(name: Option<&str>, author: Option<&str>, version: Option<&str>, functions: Vec<String>, decorators: Vec<String>) -> Self {
174        let mut stub = Self {
175            name: name.unwrap_or(&default_name()).to_string(),
176            author: author.unwrap_or(&default_author()).to_string(),
177            version: version.unwrap_or(&default_version()).to_string(),
178            contents: "".to_string(),
179            filename: "".to_string(),
180            functions: HashMap::new(),
181            decorators: HashMap::new()
182        };
183
184        for f in functions { stub.functions.insert(f.clone(), f); }
185        for d in decorators { stub.decorators.insert(d.clone(), d); }
186
187        stub
188    }
189
190    /// Attempt to load all extensions in a directory
191    pub fn load_all(directory: &str) -> Result<Vec<Extension>, std::io::Error> {
192        let mut extensions : Vec<Extension> = Vec::new();
193
194        match fs::read_dir(directory) {
195            Ok(entries) => {
196                for file in entries.flatten() {
197                    if let Some(filename) = file.path().to_str() {
198                        if let Ok(extension) = Extension::new(filename) {
199                            if filename.ends_with("js") {
200                                extensions.push(extension);
201                            }
202                        }
203                    }
204                }
205            },
206            Err(e) => {
207                return Err(e);
208            }
209        }
210
211        Ok(extensions)
212    }
213
214    /// Determine if a function exists in the extension
215    /// 
216    /// # Arguments
217    /// * `name` - Function name
218    pub fn has_function(&self, name: &str) -> bool {
219        self.functions.contains_key(name)
220    }
221
222    /// Load the script from string
223    pub fn load_script(&mut self) -> Result<Script, ParserError> {
224        match Script::from_string(&self.contents) {
225            Ok(s) => Ok(s),
226            Err(e) => Err(ParserError::Script(ScriptError::new(&e.to_string())))
227        }
228    }
229
230    /// Call a function from the extension
231    /// 
232    /// # Arguments
233    /// * `name` - Function name
234    /// * `args` - Values to pass in
235    pub fn call_function(&mut self, name: &str, args: &[Value]) -> Result<Value, ParserError> {
236        match self.load_script() {
237            Ok(mut script) => {
238                let fname = self.functions.get(name).ok_or_else(|| ParserError::FunctionName(FunctionNameError::new(name)))?;
239                let result : Result<Value, AnyError> = script.call(fname, &args.to_vec());
240                match result {
241                    Ok(v) => Ok(v),
242                    Err(e) => Err(ParserError::Script(ScriptError::new(&e.to_string())))
243                }
244            },
245            Err(e) => Err(e)
246        }
247    }
248
249    /// Determine if a decorator exists in the extension
250    /// 
251    /// # Arguments
252    /// * `name` - Decorator name
253    pub fn has_decorator(&self, name: &str) -> bool {
254        self.decorators.contains_key(name)
255    }
256
257    /// Call a decorator from the extension
258    /// 
259    /// # Arguments
260    /// * `name` - Decorator name
261    /// * `arg` - Value to pass in
262    pub fn call_decorator(&mut self, name: &str, arg: &Value) -> Result<String, ParserError> {
263        match self.load_script() {
264            Ok(mut script) => {
265                let fname = self.decorators.get(name).ok_or_else(|| ParserError::DecoratorName(DecoratorNameError::new(name)))?;
266                let result : Result<String, AnyError> = script.call(fname, &arg);
267                match result {
268                    Ok(v) => Ok(v),
269                    Err(e) => Err(ParserError::Script(ScriptError::new(&e.to_string())))
270                }
271            },
272            Err(e) => Err(e)
273        }
274    }
275
276    /// Returns the file from which an extension was loaded
277    pub fn filename(&self) -> &str {
278        &self.filename
279    }
280
281    /// Returns the name of the extension
282    pub fn name(&self) -> &str {
283        &self.name
284    }
285
286    /// Returns the name of the extension's author
287    pub fn author(&self) -> &str {
288        &self.author
289    }
290
291    /// Returns the version of the extension
292    pub fn version(&self) -> &str {
293        &self.version
294    }
295
296    /// Return the list of all functions in the extension
297    pub fn functions(&self) -> Vec<String> {
298        self.functions.keys().cloned().collect()
299    }
300
301    /// Return the list of all decorators in the extension
302    pub fn decorators(&self) -> Vec<String> {
303        self.decorators.keys().cloned().collect()
304    }
305} 
306
307/// Load a script from a string
308/// 
309/// # Arguments
310/// * `code` - JS source as string
311fn script_from_string(filename: &str, code: &str) -> Result<Extension, AnyError> {
312    let mut script = Script::from_string(code)?;
313    let mut e : Extension = script.call("extension", &())?;
314    e.contents = code.to_string();
315    e.filename = filename.to_string();
316    Ok(e)
317}
318
319#[cfg(test)]
320mod test_extensions {
321    use super::*;
322    
323    #[test]
324    fn test_new() {
325        let e = Extension::new("example_extensions/colour_utils.js").unwrap();
326        assert_eq!("HTML Colour Utilities", e.name);
327    }
328    
329    #[test]
330    fn test_to_string() {
331        let e = Extension::new("example_extensions/colour_utils.js").unwrap();
332        assert_eq!("HTML Colour Utilities v0.2.0, by @rscarson", e.to_string());
333    }
334    
335    #[test]
336    fn test_has_function() {
337        let e = Extension::new("example_extensions/colour_utils.js").unwrap();
338        assert_eq!(true, e.has_function("complement"));
339        assert_eq!(false, e.has_function("foobar"));
340    }
341    
342    #[test]
343    fn test_call_function() {
344        let mut e = Extension::new("example_extensions/colour_utils.js").unwrap();
345        assert_eq!(Value::Integer(0x00FFFF), e.call_function("complement", &[Value::Integer(0xFFAA00)]).unwrap());
346        assert_eq!(Value::Integer(0xFFF), e.call_function("color", &[Value::String("white".to_string())]).unwrap());
347    }
348    
349    #[test]
350    fn test_can_fail() {
351        let mut e = Extension::new("example_extensions/colour_utils.js").unwrap();
352        assert_eq!(true, matches!(e.call_function("complement", &[]), Err(_)));
353    }
354    
355    #[test]
356    fn test_has_decorator() {
357        let e = Extension::new("example_extensions/colour_utils.js").unwrap();
358        assert_eq!(true, e.has_decorator("color"));
359        assert_eq!(false, e.has_decorator("foobar"));
360    }
361    
362    #[test]
363    fn test_call_decorator() {
364        let mut e = Extension::new("example_extensions/colour_utils.js").unwrap();
365        assert_eq!("#ff0000", e.call_decorator("color", &Value::Integer(0xFF)).unwrap());
366    }
367    
368    #[test]
369    fn test_load_all() {
370        let e = Extension::load_all("example_extensions").unwrap();
371        assert_eq!(true, e.len() > 0);
372    }
373    
374    #[test]
375    fn test_color() {
376        let mut e = Extension::new("example_extensions/colour_utils.js").unwrap();
377        assert_eq!(Value::Integer(0x00FFFF), e.call_function("complement", &[Value::Integer(0xFFAA00)]).unwrap());
378    }
379}