script_format/
engine.rs

1use std::any::TypeId;
2use std::cell::RefCell;
3use std::iter::once;
4use std::ops::Deref;
5use std::ops::DerefMut;
6use std::path::Path;
7use std::rc::Rc;
8
9use rhai::CustomType;
10use rhai::{
11    packages::{CorePackage, Package},
12    Dynamic, Engine, EvalAltResult, ImmutableString, Scope, Variant, FLOAT, INT,
13};
14
15use crate::internal::ToBe;
16
17/// A type alias for the result of script execution within the `FormattingEngine`.
18///
19/// This alias simplifies error handling when executing Rhai scripts, encapsulating
20/// either a successful result (`T`) or an error (`Box<EvalAltResult>`).
21///
22/// # Examples
23///
24/// ```rust
25/// use script_format::{
26///     FormattingEngine,
27///     ScriptResult,
28/// };
29///
30/// fn execute_script(script: &str) -> ScriptResult<()> {
31///     let mut engine = FormattingEngine::new(false);
32///     engine.format("data", 42, script)?;
33///     Ok(())
34/// }
35/// ```
36///
37/// # Type Parameters
38///
39/// * `T` - The type of the successful result.
40pub type ScriptResult<T> = Result<T, Box<EvalAltResult>>;
41
42/// A wrapper around the Rhai `Engine` for formatting data using a dsl based on rhai.
43///
44/// `FormattingEngine` allows you to register custom types and format them using a custom dsl based on rhai.
45///
46/// # Examples
47///
48/// ```rust
49/// use script_format::FormattingEngine;
50///
51/// let mut engine = FormattingEngine::new(false);
52/// let result = engine.format("name", "World", "~ `Hello, ${name}!`");
53/// assert_eq!(result.unwrap(), "Hello, World!");
54/// ```
55///
56/// # Features
57///
58/// - Custom type registration
59/// - Script execution with data binding
60/// - Debug support for script evaluation
61pub struct FormattingEngine {
62    engine: Engine,
63    messages: Rc<RefCell<Vec<String>>>,
64}
65
66impl Deref for FormattingEngine {
67    type Target = Engine;
68
69    fn deref(&self) -> &Self::Target {
70        &self.engine
71    }
72}
73
74impl DerefMut for FormattingEngine {
75    fn deref_mut(&mut self) -> &mut Self::Target {
76        &mut self.engine
77    }
78}
79
80#[inline(always)]
81fn minus_deprecation() {
82    eprintln!(
83        "Using '-' for printing is deprecated and will be removed in future versions. Use '~' instead."
84    )
85}
86
87impl FormattingEngine {
88    fn register_value<T: Variant + Clone + std::fmt::Display>(&mut self) {
89        self.engine
90            .register_fn("++", move |a: T, b: serde_value::Value| {
91                vec![a.to_string(), serde_json::to_string(&b).unwrap()]
92            });
93        self.engine
94            .register_fn("++", move |a: serde_value::Value, b: T| {
95                vec![serde_json::to_string(&a).unwrap(), b.to_string()]
96            });
97    }
98
99    fn register_string_concat_void<T: Variant + Clone + std::fmt::Display>(&mut self) {
100        self.engine
101            .register_fn("++", move |a: T, _b: ()| vec![a.to_string()]);
102        self.engine
103            .register_fn("++", move |_a: (), b: T| vec![b.to_string()]);
104    }
105
106    fn register_string_concat<T: Variant + Clone + std::fmt::Display>(&mut self) {
107        self.engine.register_fn("++", move |a: T, b: &str| {
108            vec![a.to_string(), b.to_string()]
109        });
110        self.engine.register_fn("++", move |a: &str, b: T| {
111            vec![a.to_string(), b.to_string()]
112        });
113        self.engine
114            .register_fn("++", move |a: T, b: T| vec![a.to_string(), b.to_string()]);
115    }
116
117    fn register_string_concat_vec<T: Variant + Clone + std::fmt::Display>(&mut self) {
118        self.engine.register_fn("++", move |a: Vec<T>, b: &str| {
119            a.iter()
120                .map(ToString::to_string)
121                .chain(once(b.to_owned()))
122                .collect::<Vec<_>>()
123        });
124        self.engine.register_fn("++", move |a: &str, b: Vec<T>| {
125            b.iter()
126                .map(ToString::to_string)
127                .chain(once(a.to_owned()))
128                .collect::<Vec<_>>()
129        });
130        self.engine.register_fn("++", move |a: Vec<T>, b: Vec<T>| {
131            a.iter()
132                .map(ToString::to_string)
133                .chain(b.iter().map(ToString::to_string))
134                .collect::<Vec<_>>()
135        });
136    }
137
138    fn register_concat<T: Variant + Clone + std::fmt::Display>(&mut self) {
139        self.register_string_concat::<T>();
140        self.register_string_concat_vec::<T>();
141        self.register_string_concat_void::<T>();
142    }
143
144    fn register_msg<T: Variant + Clone + std::fmt::Display>(&mut self) {
145        self.register_msg_single::<T>();
146        self.register_msg_multi::<T, &str>();
147        self.register_msg_multi::<T, String>();
148        self.register_msg_multi::<T, bool>();
149        self.register_msg_multi::<T, i64>();
150        self.register_msg_multi::<T, u64>();
151        self.register_msg_multi::<T, i32>();
152        self.register_msg_multi::<T, u32>();
153        self.register_msg_multi::<T, i16>();
154        self.register_msg_multi::<T, u16>();
155        self.register_msg_multi::<T, i8>();
156        self.register_msg_multi::<T, u8>();
157        self.register_msg_multi::<T, usize>();
158        self.register_msg_multi::<T, isize>();
159        self.register_msg_multi::<T, i128>();
160        self.register_msg_multi::<T, u128>();
161        self.register_msg_multi::<T, f32>();
162        self.register_msg_multi::<T, f64>();
163    }
164
165    fn register_msg_multi<
166        A: Variant + Clone + std::fmt::Display,
167        B: Variant + Clone + std::fmt::Display,
168    >(
169        &mut self,
170    ) {
171        self.engine
172            .register_fn("++", move |a: A, b: B| vec![a.to_string(), b.to_string()]);
173
174        self.engine
175            .register_fn("++", move |b: B, a: A| vec![b.to_string(), a.to_string()]);
176
177        self.engine.register_fn("++", move |a: Option<A>, b: B| {
178            if let Some(a) = a {
179                vec![a.to_string(), b.to_string()]
180            } else {
181                vec![b.to_string()]
182            }
183        });
184
185        self.engine.register_fn("++", move |a: A, b: Option<B>| {
186            if let Some(b) = b {
187                vec![a.to_string(), b.to_string()]
188            } else {
189                vec![a.to_string()]
190            }
191        });
192
193        self.engine
194            .register_fn("++", move |a: Option<A>, b: Option<B>| match (a, b) {
195                (Some(a), Some(b)) => vec![a.to_string(), b.to_string()],
196                (Some(a), None) => vec![a.to_string()],
197                (None, Some(b)) => vec![b.to_string()],
198                (None, None) => vec![],
199            });
200    }
201
202    fn register_msg_single<T: Variant + Clone + std::fmt::Display>(&mut self) {
203        let messages = self.clone_messages();
204        self.engine.register_fn("-", move |msg: T| {
205            minus_deprecation();
206            messages.borrow_mut().push(msg.to_string());
207        });
208
209        self.engine.register_fn("++", move |a: Option<T>, _: ()| {
210            if let Some(a) = a {
211                vec![a.to_string()]
212            } else {
213                vec![]
214            }
215        });
216
217        self.engine.register_fn("++", move |_: (), a: Option<T>| {
218            if let Some(a) = a {
219                vec![a.to_string()]
220            } else {
221                vec![]
222            }
223        });
224
225        let messages = self.clone_messages();
226        self.engine.register_fn("-", move |msg: Option<T>| {
227            minus_deprecation();
228            if let Some(msg) = msg {
229                messages.borrow_mut().push(msg.to_string());
230            }
231        });
232    }
233
234    fn register_vec<T: Variant + Clone>(&mut self) {
235        self.engine
236            .register_type::<Vec<T>>()
237            .register_fn("len", |v: Vec<T>| v.len())
238            .register_iterator::<Vec<T>>()
239            .register_iterator::<&Vec<&T>>()
240            .register_iterator::<Vec<T>>()
241            .register_iterator::<&Vec<&T>>()
242            .register_indexer_get(|v: &mut Vec<T>, i: i64| {
243                v[usize::try_from(i).unwrap()].to_owned()
244            });
245    }
246
247    fn register_vec_printable<T: Variant + Clone + std::fmt::Display>(&mut self) {
248        self.register_vec::<T>();
249        self.engine.register_fn("++", move |a: Vec<T>| {
250            a.iter().map(ToString::to_string).collect::<Vec<_>>()
251        });
252
253        self.register_vec_printable_single::<T, &str>();
254        self.register_vec_printable_single::<T, String>();
255        self.register_vec_printable_single::<T, bool>();
256        self.register_vec_printable_single::<T, i64>();
257        self.register_vec_printable_single::<T, u64>();
258        self.register_vec_printable_single::<T, i32>();
259        self.register_vec_printable_single::<T, u32>();
260        self.register_vec_printable_single::<T, i16>();
261        self.register_vec_printable_single::<T, u16>();
262        self.register_vec_printable_single::<T, i8>();
263        self.register_vec_printable_single::<T, u8>();
264        self.register_vec_printable_single::<T, usize>();
265        self.register_vec_printable_single::<T, isize>();
266        self.register_vec_printable_single::<T, i128>();
267        self.register_vec_printable_single::<T, u128>();
268        self.register_vec_printable_single::<T, f32>();
269        self.register_vec_printable_single::<T, f64>();
270
271        self.register_vec_printable_multi::<T, &str>();
272        self.register_vec_printable_multi::<T, String>();
273        self.register_vec_printable_multi::<T, bool>();
274        self.register_vec_printable_multi::<T, i64>();
275        self.register_vec_printable_multi::<T, u64>();
276        self.register_vec_printable_multi::<T, i32>();
277        self.register_vec_printable_multi::<T, u32>();
278        self.register_vec_printable_multi::<T, i16>();
279        self.register_vec_printable_multi::<T, u16>();
280        self.register_vec_printable_multi::<T, i8>();
281        self.register_vec_printable_multi::<T, u8>();
282        self.register_vec_printable_multi::<T, usize>();
283        self.register_vec_printable_multi::<T, isize>();
284        self.register_vec_printable_multi::<T, i128>();
285        self.register_vec_printable_multi::<T, u128>();
286        self.register_vec_printable_multi::<T, f32>();
287        self.register_vec_printable_multi::<T, f64>();
288
289        self.register_vec_printable_void::<&str>();
290        self.register_vec_printable_void::<String>();
291        self.register_vec_printable_void::<bool>();
292        self.register_vec_printable_void::<i64>();
293        self.register_vec_printable_void::<u64>();
294        self.register_vec_printable_void::<i32>();
295        self.register_vec_printable_void::<u32>();
296        self.register_vec_printable_void::<i16>();
297        self.register_vec_printable_void::<u16>();
298        self.register_vec_printable_void::<i8>();
299        self.register_vec_printable_void::<u8>();
300        self.register_vec_printable_void::<usize>();
301        self.register_vec_printable_void::<isize>();
302        self.register_vec_printable_void::<i128>();
303        self.register_vec_printable_void::<u128>();
304        self.register_vec_printable_void::<f32>();
305        self.register_vec_printable_void::<f64>();
306    }
307
308    fn register_vec_printable_void<T: Variant + Clone + std::fmt::Display>(&mut self) {
309        self.engine.register_fn("++", move |a: Vec<T>, _b: ()| {
310            a.iter().map(ToString::to_string).collect::<Vec<_>>()
311        });
312        self.engine.register_fn("++", move |_a: (), b: Vec<T>| {
313            b.iter().map(ToString::to_string).collect::<Vec<_>>()
314        });
315        self.engine
316            .register_fn("++", move |a: Option<Vec<T>>, _b: ()| {
317                a.unwrap_or_default()
318                    .iter()
319                    .map(ToString::to_string)
320                    .collect::<Vec<_>>()
321            });
322        self.engine
323            .register_fn("++", move |_a: (), b: Option<Vec<T>>| {
324                b.unwrap_or_default()
325                    .iter()
326                    .map(ToString::to_string)
327                    .collect::<Vec<_>>()
328            });
329    }
330
331    fn register_vec_printable_multi<
332        A: Variant + Clone + std::fmt::Display,
333        B: Variant + Clone + std::fmt::Display,
334    >(
335        &mut self,
336    ) {
337        self.engine.register_fn("++", move |a: Vec<A>, b: Vec<B>| {
338            a.iter()
339                .map(ToString::to_string)
340                .chain(b.iter().map(ToString::to_string))
341                .collect::<Vec<_>>()
342        });
343
344        self.engine
345            .register_fn("++", move |a: Option<Vec<A>>, b: Vec<B>| {
346                let a: Vec<A> = a.unwrap_or_default();
347                a.iter()
348                    .map(ToString::to_string)
349                    .chain(b.iter().map(ToString::to_string))
350                    .collect::<Vec<_>>()
351            });
352        self.engine
353            .register_fn("++", move |a: Vec<A>, b: Option<Vec<B>>| {
354                let b: Vec<B> = b.unwrap_or_default();
355                a.iter()
356                    .map(ToString::to_string)
357                    .chain(b.iter().map(ToString::to_string))
358                    .collect::<Vec<_>>()
359            });
360    }
361
362    fn register_vec_printable_single<
363        A: Variant + Clone + std::fmt::Display,
364        B: Variant + Clone + std::fmt::Display,
365    >(
366        &mut self,
367    ) {
368        self.engine.register_fn("++", move |a: Vec<A>, b: B| {
369            a.iter()
370                .map(ToString::to_string)
371                .chain(once(b.to_string()))
372                .collect::<Vec<_>>()
373        });
374        self.engine.register_fn("++", move |a: A, b: Vec<B>| {
375            once(a.to_string())
376                .chain(b.iter().map(ToString::to_string))
377                .collect::<Vec<_>>()
378        });
379
380        self.engine
381            .register_fn("++", move |a: Option<Vec<A>>, b: B| {
382                let a: Vec<A> = a.unwrap_or_default();
383                a.iter()
384                    .map(ToString::to_string)
385                    .chain(once(b.to_string()))
386                    .collect::<Vec<_>>()
387            });
388        self.engine
389            .register_fn("++", move |a: A, b: Option<Vec<B>>| {
390                let b: Vec<B> = b.unwrap_or_default();
391                once(a.to_string())
392                    .chain(b.iter().map(ToString::to_string))
393                    .collect::<Vec<_>>()
394            });
395
396        self.engine
397            .register_fn("++", move |a: Vec<A>, b: Option<B>| {
398                let mut res = a.iter().map(ToString::to_string).collect::<Vec<_>>();
399                if let Some(b) = b {
400                    res.push(b.to_string());
401                }
402                res
403            });
404        self.engine
405            .register_fn("++", move |a: Option<A>, b: Vec<B>| {
406                a.iter()
407                    .map(ToString::to_string)
408                    .chain(b.iter().map(ToString::to_string))
409                    .collect::<Vec<_>>()
410            });
411    }
412
413    fn register_type_dynamic<T: Variant + Clone + 'static, C: From<T> + PartialEq + 'static>(
414        &mut self,
415        is_call: fn(&Dynamic) -> bool,
416        as_call: fn(&Dynamic) -> Result<C, &'static str>,
417    ) {
418        self.engine
419            .register_fn("any", move |arr: rhai::Array, v: T| {
420                let value: C = v.into();
421                arr.iter()
422                    .filter(|a| is_call(a))
423                    .map(|a| {
424                        let a: C = as_call(&a).unwrap().into();
425                        a
426                    })
427                    .filter(|a| *a == value)
428                    .into_iter()
429                    .count()
430                    > 0
431            });
432        self.engine
433            .register_fn("all", move |arr: rhai::Array, v: T| {
434                let value: C = v.into();
435                let expected = arr.len();
436                arr.iter()
437                    .filter(|a| is_call(a))
438                    .map(|a| {
439                        let a: C = as_call(&a).unwrap().into();
440                        a
441                    })
442                    .filter(|a| *a == value)
443                    .into_iter()
444                    .count()
445                    == expected
446            });
447        self.engine
448            .register_fn("none", move |arr: rhai::Array, v: T| {
449                let value: C = v.into();
450                arr.iter()
451                    .filter(|a| is_call(a))
452                    .map(|a| {
453                        let a: C = as_call(&a).unwrap().into();
454                        a
455                    })
456                    .filter(|a| *a == value)
457                    .into_iter()
458                    .count()
459                    == 0
460            });
461    }
462
463    fn register_comparison<
464        A: Variant + Clone + AsCast<C>,
465        B: Variant + Clone + AsCast<C>,
466        C: PartialEq + PartialOrd,
467    >(
468        &mut self,
469    ) {
470        self.engine
471            .register_fn(">", |left: A, right: B| left.as_cast() > right.as_cast());
472        self.engine
473            .register_fn(">=", |left: A, right: B| left.as_cast() >= right.as_cast());
474        self.engine
475            .register_fn("<", |left: A, right: B| left.as_cast() < right.as_cast());
476        self.engine
477            .register_fn("<=", |left: A, right: B| left.as_cast() <= right.as_cast());
478        self.engine
479            .register_fn("!=", |left: A, right: B| left.as_cast() != right.as_cast());
480        self.engine
481            .register_fn("==", |left: A, right: B| left.as_cast() == right.as_cast());
482
483        self.engine
484            .register_fn(">", |left: B, right: A| left.as_cast() > right.as_cast());
485        self.engine
486            .register_fn(">=", |left: B, right: A| left.as_cast() >= right.as_cast());
487        self.engine
488            .register_fn("<", |left: B, right: A| left.as_cast() < right.as_cast());
489        self.engine
490            .register_fn("<=", |left: B, right: A| left.as_cast() <= right.as_cast());
491        self.engine
492            .register_fn("!=", |left: B, right: A| left.as_cast() != right.as_cast());
493        self.engine
494            .register_fn("==", |left: B, right: A| left.as_cast() == right.as_cast());
495    }
496
497    fn register_options<T: Variant + Clone>(&mut self) {
498        self.engine
499            .register_fn("is_some", crate::internal::script_is_some::<T>)
500            .register_fn("unwrap", crate::internal::script_unwrap::<T>)
501            .register_fn("unwrap_or", crate::internal::script_unwrap_or::<T>);
502    }
503}
504
505impl FormattingEngine {
506    /// Creates a new `FormattingEngine` instance.
507    ///
508    /// # Arguments
509    ///
510    /// * `debug` - A boolean indicating whether to enable debug functions inside Rhai.
511    ///
512    /// # Returns
513    ///
514    /// A new, pre-configured, `FormattingEngine` instance.
515    #[must_use]
516    pub fn new(debug: bool) -> Self {
517        build_engine(debug)
518    }
519
520    /// Registers a custom type with the Rhai engine.
521    ///
522    /// This method also registers the `is_some`, `unwrap`, and `unwrap_or` methods for the custom type,
523    /// as well as the necessary functions for using the type inside a `Vec<T>`.
524    ///
525    /// # Type Parameters
526    ///
527    /// * `T` - The type to register, which must implement `Variant` and `Clone`.
528    ///
529    /// # Returns
530    ///
531    /// A mutable reference to `self` to allow method chaining.
532    pub fn register_type<T: Variant + Clone>(&mut self) -> &mut Self {
533        self.engine.register_type::<T>();
534        self.register_vec::<T>();
535        self.register_options::<T>();
536
537        self
538    }
539
540    /// Makes a custom type comparable.
541    ///
542    /// This method also registers the `any`, `none` and `all` methods for the custom type,
543    /// as well as the necessary functions for using the type inside a `Vec<T>`.
544    ///
545    /// # Type Parameters
546    ///
547    /// * `T` - The type to register, which must implement `Variant` and `Clone`.
548    ///
549    /// # Returns
550    ///
551    /// A mutable reference to `self` to allow method chaining.
552    pub fn make_comparable<T: Variant + PartialEq + Clone>(&mut self) -> &mut Self {
553        self.register_type::<T>();
554        self.register_options::<T>();
555        self.register_vec::<T>();
556        self.register_fn("any", crate::internal::script_any_type::<T>)
557            .register_fn("all", crate::internal::script_all_type::<T>)
558            .register_fn("none", crate::internal::script_none_type::<T>);
559        self
560    }
561
562    /// Builds and registers a custom type with the Rhai engine.
563    ///
564    /// This function initializes the custom type using `build_type` and then registers it
565    /// with the Rhai engine, making it accessible within the DSL scripts.
566    ///
567    /// # Type Parameters
568    ///
569    /// * `T` - The custom type to build and register, which must implement `Variant`, `CustomType`, and `Clone`.
570    ///
571    /// # Returns
572    ///
573    /// A mutable reference to `self` to allow method chaining.
574    ///
575    /// # Example
576    ///
577    /// ```rust
578    /// use script_format::{
579    ///     rhai::{CustomType, TypeBuilder},
580    ///     FormattingEngine,
581    /// };
582    ///
583    /// #[derive(Clone, CustomType)]
584    /// struct Person {
585    ///     name: String,
586    ///     age: i32,
587    /// }
588    ///
589    /// let mut engine = FormattingEngine::new(false);
590    /// engine.build_type::<Person>();
591    /// ```
592    pub fn build_type<T: Variant + CustomType + Clone>(&mut self) -> &mut Self {
593        self.engine.build_type::<T>();
594        self.register_type::<T>();
595        self
596    }
597
598    /// Runs the formatting script with the provided scope.
599    ///
600    /// Use the scope to pass data to the script.
601    /// **You must register (`build_type` / `register_type`) each custom type that should be accessed beforehand so that rhai can properly access it.**
602    ///
603    /// # Arguments
604    ///
605    /// * `scope` - The Rhai `Scope` to be used during script evaluation.
606    /// * `script` - The script to execute for formatting.
607    ///
608    /// # Errors
609    ///
610    /// Returns an error if script execution fails.
611    ///
612    /// # Returns
613    ///
614    /// A formatted string result.
615    pub fn format_with_scope(&mut self, scope: &mut Scope, script: &str) -> ScriptResult<String> {
616        scope.push_constant("NL", "\n");
617
618        self.messages.borrow_mut().clear();
619        self.engine.run_with_scope(scope, script)?;
620
621        Ok(self.messages.borrow().join(""))
622    }
623
624    /// Formats an object using a script from a file with the provided scope.
625    ///
626    /// Use the scope to pass data to the script.
627    /// **You must register (`build_type` / `register_type`) each custom type that should be accessed beforehand so that rhai can properly access it.**
628    ///
629    /// # Arguments
630    ///
631    /// * `scope` - The Rhai `Scope` to be used during script evaluation.
632    /// * `script` - The file path of the script to execute.
633    ///
634    /// # Errors
635    ///
636    /// Returns an error if file reading or script execution fails.
637    ///
638    /// # Returns
639    ///
640    /// A formatted string result.
641    #[cfg(not(feature = "web"))]
642    pub fn format_from_file_with_scope<P: AsRef<Path>>(
643        &mut self,
644        scope: &mut Scope,
645        script: P,
646    ) -> ScriptResult<String> {
647        match std::fs::read_to_string(script.as_ref()) {
648            Ok(script) => self.format_with_scope(scope, &script),
649            Err(e) => Err(e.to_string().into()),
650        }
651    }
652
653    /// Clones the messages buffer.
654    ///
655    /// This can be useful if you want to register a custom function that needs to access the messages buffer.
656    ///
657    /// # Returns
658    ///
659    /// A reference-counted pointer to the messages buffer.
660    pub fn clone_messages(&self) -> Rc<RefCell<Vec<String>>> {
661        self.messages.clone()
662    }
663
664    /// Formats an object using a script.
665    ///
666    /// **You must register (`build_type` / `register_type`) each custom type that should be accessed beforehand so that rhai can properly access it.**
667    ///
668    /// # Arguments
669    ///
670    /// * `name` - The name of the data variable in the script.
671    /// * `data` - The data object to format.
672    /// * `script` - The script to execute for formatting.
673    ///
674    /// # Errors
675    ///
676    /// Returns an error if script execution fails.
677    ///
678    /// # Returns
679    ///
680    /// A formatted string result.
681    pub fn format<T: Variant + Clone>(
682        &mut self,
683        name: &str,
684        data: T,
685        script: &str,
686    ) -> ScriptResult<String> {
687        self.register_type::<T>();
688
689        let mut scope = Scope::new();
690        scope.push_constant(name, data);
691
692        self.format_with_scope(&mut scope, script)
693    }
694
695    /// Formats an object using a script from a file.
696    ///
697    /// **You must register (`build_type` / `register_type`) each custom type that should be accessed beforehand so that rhai can properly access it.**
698    ///
699    /// # Arguments
700    ///
701    /// * `name` - The name of the data variable in the script.
702    /// * `data` - The data object to format.
703    /// * `script` - The file path of the script to execute.
704    ///
705    /// # Errors
706    ///
707    /// Returns an error if file reading or script execution fails.
708    ///
709    /// # Returns
710    ///
711    /// A formatted string result.
712    #[cfg(not(feature = "web"))]
713    pub fn format_from_file<T: Variant + Clone, P: AsRef<Path>>(
714        &mut self,
715        name: &str,
716        data: T,
717        script: P,
718    ) -> ScriptResult<String> {
719        match std::fs::read_to_string(script.as_ref()) {
720            Ok(script) => self.format(name, data, &script),
721            Err(e) => Err(e.to_string().into()),
722        }
723    }
724}
725
726fn option_to_string<T: std::fmt::Display>(value: Option<T>) -> Option<String> {
727    if let Some(v) = value {
728        Some(format!("{v}"))
729    } else {
730        None
731    }
732}
733
734fn dynamic_to_string(v: Dynamic) -> ScriptResult<Option<String>> {
735    let t = v.type_id();
736    if t == TypeId::of::<()>() {
737        Ok(None)
738    } else if v.is_array() {
739        let flattened = flatten_dynamic(v)?;
740        if flattened.len() > 0 {
741            Ok(Some(flattened.join("")))
742        } else {
743            Ok(None)
744        }
745    } else if t == TypeId::of::<Option<&str>>() {
746        Ok(option_to_string::<&str>(v.cast()))
747    } else if t == TypeId::of::<Option<bool>>() {
748        Ok(option_to_string::<bool>(v.cast()))
749    } else if t == TypeId::of::<Option<i8>>() {
750        Ok(option_to_string::<i8>(v.cast()))
751    } else if t == TypeId::of::<Option<i16>>() {
752        Ok(option_to_string::<i16>(v.cast()))
753    } else if t == TypeId::of::<Option<i32>>() {
754        Ok(option_to_string::<i32>(v.cast()))
755    } else if t == TypeId::of::<Option<i64>>() {
756        Ok(option_to_string::<i64>(v.cast()))
757    } else if t == TypeId::of::<Option<i128>>() {
758        Ok(option_to_string::<i128>(v.cast()))
759    } else if t == TypeId::of::<Option<u8>>() {
760        Ok(option_to_string::<u8>(v.cast()))
761    } else if t == TypeId::of::<Option<u16>>() {
762        Ok(option_to_string::<u16>(v.cast()))
763    } else if t == TypeId::of::<Option<u32>>() {
764        Ok(option_to_string::<u32>(v.cast()))
765    } else if t == TypeId::of::<Option<u64>>() {
766        Ok(option_to_string::<u64>(v.cast()))
767    } else if t == TypeId::of::<Option<f32>>() {
768        Ok(option_to_string::<f32>(v.cast()))
769    } else if t == TypeId::of::<Option<f64>>() {
770        Ok(option_to_string::<f64>(v.cast()))
771    } else if t == TypeId::of::<Option<u128>>() {
772        Ok(option_to_string::<u128>(v.cast()))
773    } else if t == TypeId::of::<Option<char>>() {
774        Ok(option_to_string::<char>(v.cast()))
775    } else if t == TypeId::of::<Option<String>>() {
776        Ok(if let Some(v) = v.cast::<Option<String>>() {
777            Some(v)
778        } else {
779            None
780        })
781    } else if t == TypeId::of::<Option<Dynamic>>() {
782        Ok(if let Some(v) = v.cast::<Option<Dynamic>>() {
783            dynamic_to_string(v)?
784        } else {
785            None
786        })
787    } else if t == TypeId::of::<bool>() {
788        Ok(Some(v.as_bool()?.to_string()))
789    } else if t == TypeId::of::<ImmutableString>() {
790        Ok(Some(v.into_immutable_string()?.to_string()))
791    } else if t == TypeId::of::<char>() {
792        Ok(Some(v.as_char()?.to_string()))
793    } else if t == TypeId::of::<INT>() {
794        Ok(Some(v.as_int()?.to_string()))
795    } else if t == TypeId::of::<FLOAT>() {
796        Ok(Some(v.as_float()?.to_string()))
797    } else {
798        eprintln!("{}", v.type_name());
799        Err("unsupported type".into())
800    }
801}
802
803fn flatten_dynamic(vs: Dynamic) -> Result<Vec<String>, Box<EvalAltResult>> {
804    let mut res = Vec::new();
805    if vs.is_array() {
806        for v in vs.into_array()? {
807            if v.is_array() {
808                let mut values = flatten_dynamic(v)?;
809                res.append(&mut values);
810            } else {
811                if let Some(s) = dynamic_to_string(v)? {
812                    res.push(s);
813                }
814            }
815        }
816    } else if vs.type_id() == TypeId::of::<Option<Vec<String>>>() {
817        if let Some(mut vs) = vs.cast::<Option<Vec<String>>>() {
818            res.append(&mut vs);
819        }
820    } else if vs.type_id() == TypeId::of::<Vec<String>>() {
821        let mut vs = vs.cast::<Vec<String>>();
822        res.append(&mut vs);
823    } else if let Some(s) = dynamic_to_string(vs)? {
824        res.push(s);
825    }
826    Ok(res)
827}
828
829#[allow(clippy::too_many_lines)]
830fn build_engine(debug: bool) -> FormattingEngine {
831    let mut engine = FormattingEngine {
832        engine: rhai::Engine::new(),
833        messages: Rc::new(RefCell::new(Vec::new())),
834    };
835    engine.set_max_expr_depths(128, 64);
836
837    let package = CorePackage::new();
838
839    engine.register_global_module(package.as_shared_module());
840
841    // Register the custom syntax
842    {
843        let messages = engine.clone_messages();
844        engine
845            .register_custom_syntax(
846                ["~", "$expr$"],
847                true,
848                move |context: &mut rhai::EvalContext,
849                      inputs: &[rhai::Expression]|
850                      -> ScriptResult<Dynamic> {
851                    for e in inputs {
852                        let result = context.eval_expression_tree(&e)?;
853                        let mut m = flatten_dynamic(result)?;
854                        messages.borrow_mut().append(&mut m);
855                    }
856                    Ok(Dynamic::UNIT)
857                },
858            )
859            .unwrap();
860    }
861
862    let indent = Rc::new(RefCell::new("    ".to_owned()));
863
864    {
865        let indent = indent.clone();
866
867        // This isn't deprecated, the api is just volatile and may change
868        #[allow(deprecated)]
869        engine.on_var(move |name, _, _| match name {
870            "IND" => Ok(Some(indent.borrow().clone().into())),
871            _ => Ok(None),
872        });
873    }
874
875    {
876        let indent = indent.clone();
877        #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
878        engine.register_fn("IND", move |count: i64| {
879            indent.borrow().repeat(count as usize)
880        });
881    }
882
883    {
884        let indent = indent.clone();
885        #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
886        engine.register_fn("SET_INDENT", move |value: &str| {
887            value.clone_into(&mut indent.borrow_mut());
888        });
889    }
890
891    {
892        let indent = indent.clone();
893        #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
894        engine.register_fn("SET_INDENT", move |value: &str, count: i64| {
895            *indent.borrow_mut() = value.repeat(count as usize)
896        });
897    }
898
899    {
900        let indent = indent.clone();
901        #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
902        engine.register_fn("SET_INDENT", move |count: i64| {
903            *indent.borrow_mut() = " ".repeat(count as usize)
904        });
905    }
906
907    #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
908    engine.register_fn("NL", |count: i64| "\n".repeat(count as usize));
909
910    engine.register_iterator::<Vec<serde_value::Value>>();
911
912    engine.register_options::<&str>();
913    engine.register_options::<String>();
914    engine.register_options::<bool>();
915    engine.register_options::<i64>();
916    engine.register_options::<u64>();
917    engine.register_options::<i32>();
918    engine.register_options::<u32>();
919    engine.register_options::<i16>();
920    engine.register_options::<u16>();
921    engine.register_options::<i8>();
922    engine.register_options::<u8>();
923    engine.register_options::<usize>();
924    engine.register_options::<isize>();
925    engine.register_options::<i128>();
926    engine.register_options::<u128>();
927    engine.register_options::<f32>();
928    engine.register_options::<f64>();
929
930    engine.register_type_dynamic::<i8, i64>(Dynamic::is_int, Dynamic::as_int);
931    engine.register_type_dynamic::<i16, i64>(Dynamic::is_int, Dynamic::as_int);
932    engine.register_type_dynamic::<i32, i64>(Dynamic::is_int, Dynamic::as_int);
933    engine.register_type_dynamic::<i64, i64>(Dynamic::is_int, Dynamic::as_int);
934
935    engine.register_type_dynamic::<u8, i64>(Dynamic::is_int, Dynamic::as_int);
936    engine.register_type_dynamic::<u16, i64>(Dynamic::is_int, Dynamic::as_int);
937    engine.register_type_dynamic::<u32, i64>(Dynamic::is_int, Dynamic::as_int);
938
939    engine.register_type_dynamic::<f32, f64>(Dynamic::is_float, Dynamic::as_float);
940    engine.register_type_dynamic::<f64, f64>(Dynamic::is_float, Dynamic::as_float);
941
942    engine.register_type_dynamic::<bool, bool>(Dynamic::is_bool, Dynamic::as_bool);
943
944    fn dynamic_as_string(dynamic: &Dynamic) -> Result<String, &'static str> {
945        dynamic.to_owned().into_string()
946    }
947
948    engine.register_type_dynamic::<String, String>(Dynamic::is_string, dynamic_as_string);
949    engine.register_type_dynamic::<&str, String>(Dynamic::is_string, dynamic_as_string);
950
951    engine
952        .register_fn("join", crate::internal::script_join)
953        .register_fn("split", crate::internal::script_split)
954        .register_fn("splitn", crate::internal::script_splitn)
955        .register_fn("rsplitn", crate::internal::script_rsplitn)
956        .register_fn("is_empty", crate::internal::script_string_is_empty)
957        .register_fn("is_empty", crate::internal::script_array_is_empty)
958        .register_fn("starts_with", crate::internal::script_starts_with)
959        .register_fn("ends_with", crate::internal::script_ends_with)
960        .register_fn("trim", crate::internal::script_trim)
961        .register_fn("is_string", crate::internal::script_is_no_string)
962        .register_fn("is_string", crate::internal::script_is_string);
963
964    // DSL
965    engine.build_type::<ToBe>();
966    engine
967        .register_custom_operator("zip", 65)
968        .unwrap()
969        .register_custom_operator("to_be", 60)
970        .unwrap()
971        .register_custom_operator("and", 60)
972        .unwrap()
973        .register_fn("and", |a: bool, b: bool| a && b)
974        .register_custom_operator("or", 30)
975        .unwrap()
976        .register_fn("or", |a: bool, b: bool| a || b)
977        .register_custom_operator("xor", 30)
978        .unwrap()
979        .register_fn("xor", |a: bool, b: bool| a ^ b)
980        .register_custom_operator("contains", 25)
981        .unwrap()
982        .register_custom_operator("equals", 25)
983        .unwrap()
984        .register_custom_operator("require", 25)
985        .unwrap()
986        .register_custom_operator("any", 25)
987        .unwrap()
988        .register_custom_operator("all", 25)
989        .unwrap()
990        .register_custom_operator("none", 25)
991        .unwrap()
992        .register_fn("contains", crate::internal::script_map_contains)
993        .register_fn("contains", crate::internal::script_string_contains)
994        .register_fn("equals", crate::internal::script_map_equals)
995        .register_fn("equals", crate::internal::script_value_equals)
996        .register_fn("equals", crate::internal::script_array_equals)
997        .register_fn("contains", crate::internal::script_array_contains)
998        .register_fn("require", crate::internal::script_require)
999        .register_fn("to_be", crate::internal::script_to_be)
1000        .register_fn("any", crate::internal::script_any)
1001        .register_fn("any", crate::internal::script_any_void)
1002        .register_fn("all", crate::internal::script_all)
1003        .register_fn("all", crate::internal::script_all_void)
1004        .register_fn("none", crate::internal::script_none)
1005        .register_fn("none", crate::internal::script_none_void);
1006
1007    engine.register_msg_single::<&str>();
1008    engine.register_msg_single::<String>();
1009    engine.register_msg_single::<bool>();
1010    engine.register_msg_single::<i64>();
1011    engine.register_msg_single::<u64>();
1012    engine.register_msg_single::<i32>();
1013    engine.register_msg_single::<u32>();
1014    engine.register_msg_single::<i16>();
1015    engine.register_msg_single::<u16>();
1016    engine.register_msg_single::<i8>();
1017    engine.register_msg_single::<u8>();
1018    engine.register_msg_single::<usize>();
1019    engine.register_msg_single::<isize>();
1020    engine.register_msg_single::<i128>();
1021    engine.register_msg_single::<u128>();
1022    engine.register_msg_single::<f32>();
1023    engine.register_msg_single::<f64>();
1024
1025    engine.register_msg::<&str>();
1026    engine.register_msg::<String>();
1027    engine.register_msg::<bool>();
1028    engine.register_msg::<i64>();
1029    engine.register_msg::<u64>();
1030    engine.register_msg::<i32>();
1031    engine.register_msg::<u32>();
1032    engine.register_msg::<i16>();
1033    engine.register_msg::<u16>();
1034    engine.register_msg::<i8>();
1035    engine.register_msg::<u8>();
1036    engine.register_msg::<usize>();
1037    engine.register_msg::<isize>();
1038    engine.register_msg::<i128>();
1039    engine.register_msg::<u128>();
1040    engine.register_msg::<f32>();
1041    engine.register_msg::<f64>();
1042
1043    engine.register_comparison::<u8, u8, u8>();
1044    engine.register_comparison::<u8, u16, u16>();
1045    engine.register_comparison::<u8, u32, u32>();
1046    engine.register_comparison::<u8, u64, u64>();
1047    engine.register_comparison::<u8, usize, u128>();
1048    engine.register_comparison::<u8, u128, u128>();
1049
1050    engine.register_comparison::<u16, u16, u16>();
1051    engine.register_comparison::<u16, u32, u32>();
1052    engine.register_comparison::<u16, u64, u64>();
1053    engine.register_comparison::<u16, usize, u128>();
1054    engine.register_comparison::<u16, u128, u128>();
1055
1056    engine.register_comparison::<u32, u32, u32>();
1057    engine.register_comparison::<u32, u64, u64>();
1058    engine.register_comparison::<u32, usize, u128>();
1059    engine.register_comparison::<u32, u128, u128>();
1060
1061    engine.register_comparison::<u64, u64, u64>();
1062    engine.register_comparison::<u64, usize, u128>();
1063    engine.register_comparison::<u64, u128, u128>();
1064
1065    engine.register_comparison::<usize, usize, u128>();
1066    engine.register_comparison::<usize, u128, u128>();
1067
1068    engine.register_comparison::<u128, u128, u128>();
1069
1070    engine.register_comparison::<i8, i8, i8>();
1071    engine.register_comparison::<i8, i16, i16>();
1072    engine.register_comparison::<i8, i32, i32>();
1073    engine.register_comparison::<i8, i64, i64>();
1074    engine.register_comparison::<i8, isize, i128>();
1075    engine.register_comparison::<i8, i128, i128>();
1076
1077    engine.register_comparison::<i16, i16, i16>();
1078    engine.register_comparison::<i16, i32, i32>();
1079    engine.register_comparison::<i16, i64, i64>();
1080    engine.register_comparison::<i8, isize, i128>();
1081    engine.register_comparison::<i16, i128, i128>();
1082
1083    engine.register_comparison::<i32, i32, i32>();
1084    engine.register_comparison::<i32, i64, i64>();
1085    engine.register_comparison::<i32, isize, i128>();
1086    engine.register_comparison::<i32, i128, i128>();
1087
1088    engine.register_comparison::<i64, i64, i64>();
1089    engine.register_comparison::<i64, isize, i128>();
1090    engine.register_comparison::<i64, i128, i128>();
1091
1092    engine.register_comparison::<isize, isize, i128>();
1093    engine.register_comparison::<isize, i128, i128>();
1094
1095    engine.register_comparison::<i128, i128, i128>();
1096
1097    engine.register_comparison::<f32, f32, f32>();
1098    engine.register_comparison::<f32, f64, f64>();
1099
1100    engine.register_comparison::<u8, f32, f32>();
1101    engine.register_comparison::<u16, f32, f32>();
1102    engine.register_comparison::<u32, f64, f64>();
1103
1104    engine.register_comparison::<i8, f32, f32>();
1105    engine.register_comparison::<i16, f32, f32>();
1106    engine.register_comparison::<i32, f64, f64>();
1107
1108    engine.register_value::<&str>();
1109    engine.register_value::<String>();
1110    engine.register_value::<bool>();
1111    engine.register_value::<i64>();
1112    engine.register_value::<u64>();
1113    engine.register_value::<i32>();
1114    engine.register_value::<u32>();
1115    engine.register_value::<i16>();
1116    engine.register_value::<u16>();
1117    engine.register_value::<i8>();
1118    engine.register_value::<u8>();
1119    engine.register_value::<usize>();
1120    engine.register_value::<isize>();
1121    engine.register_value::<i128>();
1122    engine.register_value::<u128>();
1123    engine.register_value::<f32>();
1124    engine.register_value::<f64>();
1125
1126    {
1127        let messages = engine.clone_messages();
1128        engine.register_fn("-", move |msg: Dynamic| -> ScriptResult<()> {
1129            minus_deprecation();
1130            if msg.is_array() {
1131                let arr = msg.into_array().unwrap();
1132                for m in arr {
1133                    if let Some(msg) = dynamic_to_string(m)? {
1134                        messages.borrow_mut().push(msg);
1135                    }
1136                }
1137            } else {
1138                if let Some(msg) = dynamic_to_string(msg)? {
1139                    messages.borrow_mut().push(msg);
1140                }
1141            }
1142            Ok(())
1143        });
1144    }
1145
1146    let messages = engine.clone_messages();
1147    engine.register_fn("-", move |msg: serde_value::Value| {
1148        minus_deprecation();
1149        messages
1150            .borrow_mut()
1151            .push(serde_json::to_string(&msg).unwrap());
1152    });
1153
1154    engine.register_fn("++", move |a: serde_value::Value, b: serde_value::Value| {
1155        vec![
1156            serde_json::to_string(&a).unwrap(),
1157            serde_json::to_string(&b).unwrap(),
1158        ]
1159    });
1160
1161    engine.register_concat::<&str>();
1162    engine.register_concat::<String>();
1163    engine.register_concat::<bool>();
1164    engine.register_concat::<i64>();
1165    engine.register_concat::<u64>();
1166    engine.register_concat::<i32>();
1167    engine.register_concat::<u32>();
1168    engine.register_concat::<i16>();
1169    engine.register_concat::<u16>();
1170    engine.register_concat::<i8>();
1171    engine.register_concat::<u8>();
1172    engine.register_concat::<usize>();
1173    engine.register_concat::<isize>();
1174    engine.register_concat::<i128>();
1175    engine.register_concat::<u128>();
1176    engine.register_concat::<f32>();
1177    engine.register_concat::<f64>();
1178
1179    engine.register_vec_printable::<&str>();
1180    engine.register_vec_printable::<String>();
1181    engine.register_vec_printable::<bool>();
1182    engine.register_vec_printable::<i64>();
1183    engine.register_vec_printable::<u64>();
1184    engine.register_vec_printable::<i32>();
1185    engine.register_vec_printable::<u32>();
1186    engine.register_vec_printable::<i16>();
1187    engine.register_vec_printable::<u16>();
1188    engine.register_vec_printable::<i8>();
1189    engine.register_vec_printable::<u8>();
1190    engine.register_vec_printable::<usize>();
1191    engine.register_vec_printable::<isize>();
1192    engine.register_vec_printable::<i128>();
1193    engine.register_vec_printable::<u128>();
1194    engine.register_vec_printable::<f32>();
1195    engine.register_vec_printable::<f64>();
1196
1197    engine.register_vec::<()>();
1198
1199    engine
1200        .register_fn("any", crate::internal::script_any_type::<bool>)
1201        .register_fn("all", crate::internal::script_any_type::<bool>)
1202        .register_fn("none", crate::internal::script_any_type::<bool>)
1203        .register_fn("++", move |(): (), b: &str| vec![b.to_owned()])
1204        .register_fn("++", move |(): (), b: usize| vec![b.to_string()]);
1205    engine
1206        .register_custom_operator("++", 15)
1207        .unwrap()
1208        .register_custom_operator("then_emit", 20)
1209        .unwrap()
1210        .register_fn(
1211            "then_emit",
1212            move |a: bool, msg: &str| {
1213                if a {
1214                    Some(msg.to_owned())
1215                } else {
1216                    None
1217                }
1218            },
1219        )
1220        .register_fn(
1221            "then_emit",
1222            move |a: bool, msg: Vec<String>| {
1223                if a {
1224                    msg
1225                } else {
1226                    Vec::new()
1227                }
1228            },
1229        )
1230        .register_custom_operator("or_emit", 20)
1231        .unwrap()
1232        .register_fn(
1233            "or_emit",
1234            move |a: bool, msg: &str| {
1235                if !a {
1236                    Some(msg.to_owned())
1237                } else {
1238                    None
1239                }
1240            },
1241        )
1242        .register_fn(
1243            "or_emit",
1244            move |a: bool, msg: Vec<String>| {
1245                if a {
1246                    msg
1247                } else {
1248                    Vec::new()
1249                }
1250            },
1251        );
1252    // END DSL
1253
1254    if debug {
1255        engine.on_print(move |x| eprintln!("INFO => {x}"));
1256        engine.on_debug(move |x, _, pos| eprintln!("DEBUG({pos:?}) => {x}"));
1257    } else {
1258        engine.on_print(|_| ());
1259        engine.on_debug(|_, _, _| ());
1260    }
1261
1262    engine.disable_symbol("eval");
1263
1264    engine
1265}
1266
1267macro_rules! impl_as_cast {
1268    ($A: ty, ($($B: ty),*)) => {
1269        $(
1270            impl AsCast<$B> for $A {
1271                fn as_cast(self) -> $B {
1272                    self as $B
1273                }
1274            }
1275        )*
1276    }
1277}
1278
1279trait AsCast<T> {
1280    fn as_cast(self) -> T;
1281}
1282
1283impl_as_cast!(u8, (u8, u16, u32, u64, u128, usize));
1284impl_as_cast!(i8, (i8, i16, i32, i64, i128, isize));
1285
1286impl_as_cast!(u16, (u16, u32, u64, u128, usize));
1287impl_as_cast!(i16, (i16, i32, i64, i128, isize));
1288
1289impl_as_cast!(u32, (u32, u64, u128, usize));
1290impl_as_cast!(i32, (i32, i64, i128, isize));
1291
1292impl_as_cast!(u64, (u64, u128));
1293impl_as_cast!(i64, (i64, i128));
1294
1295impl_as_cast!(usize, (usize, u128));
1296impl_as_cast!(isize, (isize, i128));
1297
1298impl_as_cast!(u128, (u128));
1299impl_as_cast!(i128, (i128));
1300
1301impl_as_cast!(f32, (f32, f64));
1302impl_as_cast!(f64, (f64));
1303
1304impl_as_cast!(u8, (f32, f64));
1305impl_as_cast!(u16, (f32, f64));
1306impl_as_cast!(u32, (f64));
1307
1308impl_as_cast!(i8, (f32, f64));
1309impl_as_cast!(i16, (f32, f64));
1310impl_as_cast!(i32, (f64));