use log::*; use std::io::Write; use std::process::{Command, Stdio, Child}; use which::which; use crate::meta_plugin::{MetaPlugin, MetaPluginResponse, MetaPluginType}; pub struct MetaPluginExec { pub program: String, pub args: Vec, pub supported: bool, pub split_whitespace: bool, process: Option, writer: Option>, result: Option, outputs: std::collections::HashMap, options: std::collections::HashMap, } // Manual Debug implementation because Box doesn't implement Debug impl std::fmt::Debug for MetaPluginExec { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("MetaPluginExec") .field("program", &self.program) .field("args", &self.args) .field("supported", &self.supported) .field("split_whitespace", &self.split_whitespace) .field("process", &self.process) .field("writer", &self.writer.as_ref().map(|_| "Box")) .field("result", &self.result) .field("outputs", &self.outputs) .field("options", &self.options) .finish() } } impl MetaPluginExec { pub fn new( program: &str, args: Vec<&str>, meta_name: String, split_whitespace: bool, _options: Option>, outputs: Option>, ) -> MetaPluginExec { let program_path = which(program); let supported = program_path.is_ok(); // Start with default outputs let mut final_outputs = std::collections::HashMap::new(); final_outputs.insert(meta_name.clone(), serde_yaml::Value::String(meta_name.clone())); if let Some(outs) = outputs { for (key, value) in outs { final_outputs.insert(key, value); } } // Start with default options let mut final_options = std::collections::HashMap::new(); if let Some(opts) = _options { for (key, value) in opts { final_options.insert(key, value); } } MetaPluginExec { program: program_path.map_or_else(|_| program.to_string(), |p| p.to_string_lossy().to_string()), args: args.iter().map(|s| s.to_string()).collect(), supported, split_whitespace, process: None, writer: None, result: None, outputs: final_outputs, options: final_options, } } } impl MetaPlugin for MetaPluginExec { fn is_supported(&self) -> bool { self.supported } fn is_internal(&self) -> bool { false } fn initialize(&mut self) -> MetaPluginResponse { debug!("META: Initializing program plugin: {:?}", self); let program = self.program.clone(); let args = self.args.clone(); debug!("META: Executing command: {:?} {:?}", program, args); let mut process = match Command::new(program.clone()) .args(args.clone()) .stdin(Stdio::piped()) .stdout(Stdio::piped()) .stderr(Stdio::piped()) .spawn() { Ok(process) => process, Err(e) => { debug!("META: Failed to spawn process: {}", e); return MetaPluginResponse { metadata: Vec::new(), is_finalized: true, }; } }; let stdin = process.stdin.take().unwrap(); self.writer = Some(Box::new(stdin)); self.process = Some(process); MetaPluginResponse { metadata: Vec::new(), is_finalized: false, } } fn finalize(&mut self) -> MetaPluginResponse { debug!("META: Finalizing program plugin"); let mut metadata = Vec::new(); if let Some(process) = self.process.take() { // Close stdin to signal end of input drop(self.writer.take()); // Wait for the process to complete let output = match process.wait_with_output() { Ok(output) => output, Err(e) => { debug!("META: Failed to get process output: {}", e); return MetaPluginResponse { metadata: Vec::new(), is_finalized: true, }; } }; if output.status.success() { // Process the output let output_str = String::from_utf8_lossy(&output.stdout); let result = if self.split_whitespace { output_str.split_whitespace().next().unwrap_or("").to_string() } else { output_str.trim().to_string() }; if !result.is_empty() { debug!("META: Program output: {}", result); self.result = Some(result.clone()); // Use process_metadata_outputs to handle output mapping if let Some(meta_data) = crate::meta_plugin::process_metadata_outputs( &self.meta_type().to_string(), serde_yaml::Value::String(result), &self.outputs ) { metadata.push(meta_data); } } } else { debug!("META: Program failed with status: {:?}", output.status); let stderr = String::from_utf8_lossy(&output.stderr); if !stderr.is_empty() { debug!("META: Program stderr: {}", stderr); } } } MetaPluginResponse { metadata, is_finalized: true, } } fn update(&mut self, data: &[u8]) -> MetaPluginResponse { if let Some(ref mut writer) = self.writer { if let Err(e) = writer.write_all(data) { debug!("META: Failed to write to process stdin: {}", e); } } MetaPluginResponse { metadata: Vec::new(), is_finalized: false, } } fn meta_type(&self) -> MetaPluginType { MetaPluginType::Exec } fn program_info(&self) -> Option<(&str, Vec<&str>)> { if self.supported { Some((&self.program, self.args.iter().map(|s| s.as_str()).collect())) } else { None } } fn outputs(&self) -> &std::collections::HashMap { &self.outputs } fn outputs_mut(&mut self) -> &mut std::collections::HashMap { &mut self.outputs } fn default_outputs(&self) -> Vec { vec![self.meta_type().to_string()] } fn options(&self) -> &std::collections::HashMap { &self.options } fn options_mut(&mut self) -> &mut std::collections::HashMap { &mut self.options } }