Repository: comtrya/comtrya (language: rust)

Issue:
Swap Koto for Rhai?
Putting this in the maybe pile.

Pro of Rhai? Expression only mode and a simpler context model

https://rhai.rs/book/engine/expressions.html

As we'd only be using expressions, the user facing API for variants likely wouldn't be affected.

Rhai also has considerably better documentation.


Discussion / hints:
Request for comments @bketelsen @icepuma 
Hmm. Regarding the Plugin - Feature. Should I just drop the `koto` - Runtime?
@icepuma Is that a major problem / work?
Nope, because the feature is not done, it's easy to just drop it.

:+1: for `Rhai`

Fix patch:
diff --git a/Cargo.toml b/Cargo.toml
index 2f1b84f4..27e2c830 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -15,10 +15,6 @@ file_diff = "1.0.0"
 fs_extra = "1.2.0"
 gitsync = "0.1.6"
 ignore = "0.4.18"
-koto = "0.10.0"
-koto_json = "0.10.0"
-koto_runtime = "0.10.0"
-koto_yaml = "0.10.0"
 normpath = "0.3.2"
 os_info = "3.4.0"
 paw = "1.0"
@@ -29,6 +25,7 @@ reqwest = { version = "0.11.10", default-features = false, features = [
     "blocking",
     "rustls-tls"
 ] }
+rhai = { version = "1.7.0", features = ["serde"] }
 serde = { version = "1.0", features = ["derive"] }
 serde_json = "1.0.81"
 serde_yaml = "0.8.24"
diff --git a/src/actions/mod.rs b/src/actions/mod.rs
index 3a000b8e..8ce621c9 100644
--- a/src/actions/mod.rs
+++ b/src/actions/mod.rs
@@ -5,7 +5,7 @@ mod git;
 mod macos;
 mod package;
 
-use crate::contexts::{to_koto, Contexts};
+use crate::contexts::Contexts;
 use crate::manifests::Manifest;
 use crate::steps::Step;
 use command::run::RunCommand;
@@ -14,10 +14,11 @@ use file::copy::FileCopy;
 use file::download::FileDownload;
 use file::link::FileLink;
 use git::GitClone;
-use koto::{Koto, KotoSettings};
 use macos::MacOSDefault;
 use package::install::PackageInstall;
+use rhai::Engine;
 use serde::{Deserialize, Serialize};
+use tracing::error;
 
 #[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Default)]
 pub struct ConditionalVariantAction<T> {
@@ -45,6 +46,9 @@ where
     T: Action,
 {
     fn plan(&self, manifest: &Manifest, context: &Contexts) -> Vec<Step> {
+        let engine = Engine::new();
+        let mut scope = crate::contexts::to_rhai(context);
+
         let variant = self.variants.iter().find(|variant| {
             if variant.condition.is_none() {
                 return false;
@@ -52,24 +56,12 @@ where
 
             let condition = variant.condition.clone().unwrap();
 
-            let mut koto = Koto::with_settings(KotoSettings {
-                run_tests: false,
-                ..Default::default()
-            });
-
-            for (key, value) in context {
-                koto.prelude().add_value(key, to_koto(value));
-            }
-
-            match koto.compile(&condition) {
-                Ok(_) => match koto.run() {
-                    Ok(result) => match result {
-                        koto_runtime::Value::Bool(result) => result,
-                        _ => false,
-                    },
-                    Err(_) => false,
-                },
-                Err(_) => false,
+            match engine.eval_with_scope::<bool>(&mut scope, condition.as_str()) {
+                Ok(b) => b,
+                Err(error) => {
+                    error!("Failed execution condition for action: {}", error);
+                    false
+                }
             }
         });
 
@@ -81,27 +73,15 @@ where
             return self.action.plan(manifest, context);
         }
 
-        let mut koto = Koto::with_settings(KotoSettings {
-            run_tests: false,
-            ..Default::default()
-        });
-
-        for (key, value) in context {
-            koto.prelude().add_value(key, to_koto(value));
-        }
+        let condition = self.condition.clone().unwrap();
 
-        match koto.compile(self.condition.clone().unwrap().as_str()) {
-            Ok(_) => match koto.run() {
-                Ok(result) => match result {
-                    koto_runtime::Value::Bool(result) => match result {
-                        true => self.action.plan(manifest, context),
-                        false => vec![],
-                    },
-                    _ => vec![],
-                },
-                Err(_) => vec![],
-            },
-            Err(_) => vec![],
+        match engine.eval_with_scope::<bool>(&mut scope, condition.as_str()) {
+            Ok(true) => self.action.plan(manifest, context),
+            Ok(false) => vec![],
+            Err(error) => {
+                error!("Failed execution condition for action: {}", error);
+                vec![]
+            }
         }
     }
 }
diff --git a/src/contexts/mod.rs b/src/contexts/mod.rs
index 938cbd62..49b8ee8e 100644
--- a/src/contexts/mod.rs
+++ b/src/contexts/mod.rs
@@ -1,3 +1,4 @@
+use rhai::Scope;
 use serde::{Deserialize, Serialize};
 use serde_yaml::Value;
 use std::collections::BTreeMap;
@@ -83,31 +84,48 @@ pub fn to_tera(contexts: &Contexts) -> tera::Context {
     context
 }
 
-pub fn to_koto(context: &BTreeMap<String, Value>) -> koto::runtime::Value {
-    koto_yaml::yaml_value_to_koto_value(&serde_yaml::to_value(context).unwrap()).unwrap()
+pub fn to_rhai(context: &Contexts) -> rhai::Scope {
+    let mut scope = Scope::new();
+
+    context.iter().for_each(|(m, v)| {
+        let dynamic = match rhai::serde::to_dynamic(v) {
+            Ok(dynamic) => dynamic,
+            Err(error) => {
+                panic!("Failed to convert context value to dynamic: {}", error);
+            }
+        };
+
+        scope.push_constant(m.clone(), dynamic);
+
+        ()
+    });
+
+    scope
 }
 
 #[cfg(test)]
 mod test {
     use super::*;
+    use rhai::Engine;
     use serde_yaml::Value;
 
     #[test]
-    fn it_can_convert_to_tera() {
+    fn it_can_convert_to_rhai() {
+        let engine = Engine::new();
+
         let mut contexts: Contexts = BTreeMap::new();
         let mut user_context: BTreeMap<String, Value> = BTreeMap::new();
 
         user_context.insert(String::from("username"), String::from("rawkode").into());
         contexts.insert(String::from("user"), user_context);
 
-        let tera_context = to_tera(&contexts);
-        assert_eq!(true, tera_context.contains_key("user"));
-        let tera_user_context = tera_context.get("user").unwrap();
-        assert_eq!(true, tera_user_context.get("username").is_some());
-        assert_eq!(
-            String::from("rawkode"),
-            tera_user_context.get("username").unwrap().as_str().unwrap()
-        );
+        let mut rhai_context = to_rhai(&contexts);
+        assert!(rhai_context.contains("user"));
+
+        let result = engine
+            .eval_with_scope::<String>(&mut rhai_context, "user.username")
+            .unwrap();
+        assert_eq!(result, String::from("rawkode"));
     }
 
     #[test]


Test patch: