Skip to main content

plexus_substrate/activations/bash/
activation.rs

1use super::executor::BashExecutor;
2use super::types::BashEvent;
3use futures::Stream;
4use plexus_macros::activation;
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#[plexus_macros::activation(namespace = "bash",
45version = "1.0.0",
46description = "Execute bash commands and stream output", crate_path = "plexus_core")]
47impl Bash {
48    /// Execute a bash command and stream stdout, stderr, and exit code
49    #[plexus_macros::method]
50    async fn execute(&self, command: String) -> impl Stream<Item = BashEvent> + Send + 'static {
51        self.executor.execute(&command).await
52    }
53}
54
55#[cfg(test)]
56mod tests {
57    use super::*;
58    use crate::plexus::Activation;
59
60    #[test]
61    fn test_bash_activation_trait() {
62        let bash = Bash::new();
63        assert_eq!(bash.namespace(), "bash");
64        assert_eq!(bash.version(), "1.0.0");
65        assert!(bash.methods().contains(&"execute"));
66    }
67
68    #[test]
69    fn test_bash_method_help() {
70        let bash = Bash::new();
71        let help = bash.method_help("execute");
72        assert!(help.is_some());
73        assert!(help.unwrap().contains("Execute"));
74    }
75
76    #[test]
77    fn test_bash_namespace_constant() {
78        assert_eq!(Bash::NAMESPACE, "bash");
79    }
80
81    #[test]
82    fn test_generated_method_enum() {
83        let names = BashMethod::all_method_names();
84        assert!(names.contains(&"execute"));
85    }
86
87    #[test]
88    fn test_plugin_schema_with_return_types() {
89        use crate::plexus::Activation;
90        let bash = Bash::new();
91        let schema = bash.plugin_schema();
92
93        assert_eq!(schema.namespace, "bash");
94        assert_eq!(schema.version, "1.0.0");
95        // 2 methods: user-defined "execute" + auto-generated "schema"
96        assert_eq!(schema.methods.len(), 2);
97        assert!(schema.is_leaf(), "bash should be a leaf plugin");
98
99        let execute = schema.methods.iter().find(|m| m.name == "execute")
100            .expect("should have an 'execute' method");
101        assert!(execute.params.is_some(), "should have params schema");
102        assert!(execute.returns.is_some(), "should have returns schema");
103
104        // Print the plugin schema
105        let json = serde_json::to_string_pretty(&schema).unwrap();
106        println!("Bash plugin_schema():\n{}", json);
107
108        // Verify returns includes BashEvent variants
109        let returns_json = serde_json::to_string(&execute.returns).unwrap();
110        assert!(returns_json.contains("stdout") || returns_json.contains("Stdout"));
111        assert!(returns_json.contains("stderr") || returns_json.contains("Stderr"));
112        assert!(returns_json.contains("exit") || returns_json.contains("Exit"));
113    }
114}