Skip to main content

plexus_substrate/activations/bash/
activation.rs

1use super::executor::BashExecutor;
2use super::types::BashEvent;
3use futures::Stream;
4use plexus_macros::hub_methods;
5
6/// Bash activation - execute shell commands and stream output
7#[derive(Clone)]
8pub struct Bash {
9    executor: BashExecutor,
10}
11
12impl Bash {
13    pub fn new() -> Self {
14        Self {
15            executor: BashExecutor::new(),
16        }
17    }
18
19    /// Register default templates with the mustache plugin
20    ///
21    /// Call this during initialization to register Bash's default templates
22    /// for rendering command output.
23    pub async fn register_default_templates(
24        &self,
25        mustache: &crate::activations::mustache::Mustache,
26    ) -> Result<(), String> {
27        let plugin_id = Self::PLUGIN_ID;
28
29        mustache.register_templates(plugin_id, &[
30            // Execute method - command output
31            ("execute", "default", "{{#stdout}}{{stdout}}{{/stdout}}{{#stderr}}\n[stderr] {{stderr}}{{/stderr}}{{#exit_code}}\n[exit: {{exit_code}}]{{/exit_code}}"),
32            ("execute", "compact", "{{stdout}}"),
33            ("execute", "verbose", "$ {{command}}\n{{stdout}}{{#stderr}}\n--- stderr ---\n{{stderr}}{{/stderr}}\n[exit code: {{exit_code}}]"),
34        ]).await
35    }
36}
37
38impl Default for Bash {
39    fn default() -> Self {
40        Self::new()
41    }
42}
43
44#[hub_methods(
45    namespace = "bash",
46    version = "1.0.0",
47    description = "Execute bash commands and stream output"
48)]
49impl Bash {
50    /// Execute a bash command and stream stdout, stderr, and exit code
51    #[plexus_macros::hub_method]
52    async fn execute(&self, command: String) -> impl Stream<Item = BashEvent> + Send + 'static {
53        self.executor.execute(&command).await
54    }
55}
56
57#[cfg(test)]
58mod tests {
59    use super::*;
60    use crate::plexus::Activation;
61
62    #[test]
63    fn test_bash_activation_trait() {
64        let bash = Bash::new();
65        assert_eq!(bash.namespace(), "bash");
66        assert_eq!(bash.version(), "1.0.0");
67        assert!(bash.methods().contains(&"execute"));
68    }
69
70    #[test]
71    fn test_bash_method_help() {
72        let bash = Bash::new();
73        let help = bash.method_help("execute");
74        assert!(help.is_some());
75        assert!(help.unwrap().contains("Execute"));
76    }
77
78    #[test]
79    fn test_bash_namespace_constant() {
80        assert_eq!(Bash::NAMESPACE, "bash");
81    }
82
83    #[test]
84    fn test_generated_method_enum() {
85        let names = BashMethod::all_method_names();
86        assert!(names.contains(&"execute"));
87    }
88
89    #[test]
90    fn test_plugin_schema_with_return_types() {
91        use crate::plexus::Activation;
92        let bash = Bash::new();
93        let schema = bash.plugin_schema();
94
95        assert_eq!(schema.namespace, "bash");
96        assert_eq!(schema.version, "1.0.0");
97        // 2 methods: user-defined "execute" + auto-generated "schema"
98        assert_eq!(schema.methods.len(), 2);
99        assert!(schema.is_leaf(), "bash should be a leaf plugin");
100
101        let execute = schema.methods.iter().find(|m| m.name == "execute")
102            .expect("should have an 'execute' method");
103        assert!(execute.params.is_some(), "should have params schema");
104        assert!(execute.returns.is_some(), "should have returns schema");
105
106        // Print the plugin schema
107        let json = serde_json::to_string_pretty(&schema).unwrap();
108        println!("Bash plugin_schema():\n{}", json);
109
110        // Verify returns includes BashEvent variants
111        let returns_json = serde_json::to_string(&execute.returns).unwrap();
112        assert!(returns_json.contains("stdout") || returns_json.contains("Stdout"));
113        assert!(returns_json.contains("stderr") || returns_json.contains("Stderr"));
114        assert!(returns_json.contains("exit") || returns_json.contains("Exit"));
115    }
116}