diff --git a/src/filter_plugin/grep.rs b/src/filter_plugin/grep.rs index cc38415..929fe45 100644 --- a/src/filter_plugin/grep.rs +++ b/src/filter_plugin/grep.rs @@ -70,7 +70,7 @@ impl GrepFilter { /// filter.filter(&mut input, &mut output)?; /// ``` impl FilterPlugin for GrepFilter { - fn filter(&mut self, reader: Box<&mut dyn Read>, writer: Box<&mut dyn Write>) -> Result<()> { + fn filter(&mut self, mut reader: Box, mut writer: Box) -> Result<()> { let mut buf_reader = std::io::BufReader::new(reader.as_mut()); for line in buf_reader.by_ref().lines() { let line = line?; diff --git a/src/filter_plugin/head.rs b/src/filter_plugin/head.rs index ec2ce6d..168c1a3 100644 --- a/src/filter_plugin/head.rs +++ b/src/filter_plugin/head.rs @@ -72,7 +72,7 @@ impl HeadBytesFilter { /// // Input "Hello World" becomes "Hello" /// ``` impl FilterPlugin for HeadBytesFilter { - fn filter(&mut self, reader: Box<&mut dyn Read>, writer: Box<&mut dyn Write>) -> Result<()> { + fn filter(&mut self, mut reader: Box, mut writer: Box) -> Result<()> { if self.remaining == 0 { return Ok(()); } @@ -182,7 +182,7 @@ impl HeadLinesFilter { /// // Input "Line1\nLine2\nLine3" becomes "Line1\nLine2\n" /// ``` impl FilterPlugin for HeadLinesFilter { - fn filter(&mut self, reader: Box<&mut dyn Read>, writer: Box<&mut dyn Write>) -> Result<()> { + fn filter(&mut self, mut reader: Box, mut writer: Box) -> Result<()> { if self.remaining == 0 { return Ok(()); } diff --git a/src/filter_plugin/mod.rs b/src/filter_plugin/mod.rs index 8f58e8c..887ca8e 100644 --- a/src/filter_plugin/mod.rs +++ b/src/filter_plugin/mod.rs @@ -101,7 +101,7 @@ pub trait FilterPlugin: Send { /// // ... other methods /// } /// ``` - fn filter(&mut self, reader: Box<&mut dyn Read>, writer: Box<&mut dyn Write>) -> Result<()>; + fn filter(&mut self, mut reader: Box, mut writer: Box) -> Result<()>; /// Clones this plugin into a new boxed instance. /// @@ -295,16 +295,16 @@ impl FilterChain { for i in 0..plugins_len { // Create a cursor for the current data let mut cursor = std::io::Cursor::new(¤t_data); - let input: Box<&mut dyn Read> = Box::new(&mut cursor); + let input: Box = Box::new(cursor); // For the last plugin, write directly to the output writer if i == plugins_len - 1 { - let output: Box<&mut dyn Write> = Box::new(writer); + let output: Box = Box::new(writer); self.plugins[i].filter(input, output)?; } else { // For intermediate plugins, write to a buffer let mut output = Vec::new(); - let output_ref: Box<&mut dyn Write> = Box::new(&mut output); + let output_ref: Box = Box::new(&mut output); self.plugins[i].filter(input, output_ref)?; current_data = output; } diff --git a/src/filter_plugin/skip.rs b/src/filter_plugin/skip.rs index e18cef4..6ba7a6d 100644 --- a/src/filter_plugin/skip.rs +++ b/src/filter_plugin/skip.rs @@ -32,7 +32,7 @@ impl FilterPlugin for SkipBytesFilter { /// # Returns /// /// Returns `Ok(())` on success, or an `io::Error` if reading or writing fails. - fn filter(&mut self, reader: Box<&mut dyn Read>, writer: Box<&mut dyn Write>) -> Result<()> { + fn filter(&mut self, mut reader: Box, mut writer: Box) -> Result<()> { // Skip bytes in chunks if self.remaining > 0 { let mut buffer = vec![0; PIPESIZE]; @@ -107,7 +107,7 @@ impl FilterPlugin for SkipLinesFilter { /// # Returns /// /// Returns `Ok(())` on success, or an `io::Error` if reading or writing fails. - fn filter(&mut self, reader: Box<&mut dyn Read>, writer: Box<&mut dyn Write>) -> Result<()> { + fn filter(&mut self, mut reader: Box, mut writer: Box) -> Result<()> { let mut buf_reader = std::io::BufReader::new(reader.as_mut()); for line in buf_reader.by_ref().lines() { let line = line?; diff --git a/src/filter_plugin/strip_ansi.rs b/src/filter_plugin/strip_ansi.rs index 9194319..6466bd4 100644 --- a/src/filter_plugin/strip_ansi.rs +++ b/src/filter_plugin/strip_ansi.rs @@ -32,7 +32,7 @@ impl FilterPlugin for StripAnsiFilter { /// # Returns /// /// Returns `Ok(())` on success, or an `io::Error` if reading or writing fails. - fn filter(&mut self, reader: Box<&mut dyn Read>, writer: Box<&mut dyn Write>) -> Result<()> { + fn filter(&mut self, mut reader: Box, mut writer: Box) -> Result<()> { let mut ansi_writer = Writer::new(writer.as_mut()); std::io::copy(reader.as_mut(), &mut ansi_writer)?; ansi_writer.flush()?; diff --git a/src/filter_plugin/tail.rs b/src/filter_plugin/tail.rs index d05fbf6..41cac58 100644 --- a/src/filter_plugin/tail.rs +++ b/src/filter_plugin/tail.rs @@ -35,7 +35,7 @@ impl FilterPlugin for TailBytesFilter { /// # Returns /// /// Returns `Ok(())` on success, or an `io::Error` if reading or writing fails. - fn filter(&mut self, reader: Box<&mut dyn Read>, writer: Box<&mut dyn Write>) -> Result<()> { + fn filter(&mut self, mut reader: Box, mut writer: Box) -> Result<()> { let mut temp_buffer = vec![0; PIPESIZE]; loop { let bytes_read = reader.as_mut().read(&mut temp_buffer)?; @@ -117,7 +117,7 @@ impl FilterPlugin for TailLinesFilter { /// # Returns /// /// Returns `Ok(())` on success, or an `io::Error` if reading or writing fails. - fn filter(&mut self, reader: Box<&mut dyn Read>, writer: Box<&mut dyn Write>) -> Result<()> { + fn filter(&mut self, mut reader: Box, mut writer: Box) -> Result<()> { let mut buf_reader = std::io::BufReader::new(reader.as_mut()); for line in buf_reader.by_ref().lines() { let line = line?; diff --git a/src/meta_plugin/exec.rs b/src/meta_plugin/exec.rs index 2904d95..250a990 100644 --- a/src/meta_plugin/exec.rs +++ b/src/meta_plugin/exec.rs @@ -83,7 +83,7 @@ impl MetaPluginExec { // Set default output let default_outputs = &[meta_name.as_str()]; - base.initialize_plugin(default_outputs, _options, outputs); + base.initialize_plugin(default_outputs, &_options, &outputs); MetaPluginExec { program: program.to_string(), @@ -128,7 +128,7 @@ impl MetaPluginExec { match cmd.spawn() { Ok(child) => { - let stdin = child.stdin.unwrap(); + let stdin = child.stdin.take().unwrap(); self.writer = Some(Box::new(stdin)); self.process = Some(child); debug!("META: Exec plugin: started process for '{}'", self.program); @@ -184,7 +184,7 @@ impl MetaPlugin for MetaPluginExec { drop(self.writer.take()); // Wait for process to complete and capture output - if let Some(mut child) = self.process.take() { + if let Some(child) = self.process.take() { match child.wait_with_output() { Ok(output) => { if output.status.success() { diff --git a/src/meta_plugin/hostname.rs b/src/meta_plugin/hostname.rs index cc1d56f..f943a7b 100644 --- a/src/meta_plugin/hostname.rs +++ b/src/meta_plugin/hostname.rs @@ -18,7 +18,7 @@ impl HostnameMetaPlugin { // Set default outputs let default_outputs = &["hostname", "hostname_full", "hostname_short"]; - base.initialize_plugin(default_outputs, options, outputs); + base.initialize_plugin(default_outputs, &options, &outputs); // Start with default options - hostname is now boolean only base.options.insert("hostname".to_string(), serde_yaml::Value::Bool(true)); @@ -26,20 +26,20 @@ impl HostnameMetaPlugin { base.options.insert("hostname_short".to_string(), serde_yaml::Value::Bool(true)); // Override with provided options - if let Some(opts) = options { + if let Some(opts) = &options { for (key, value) in opts { // Convert string "true"/"false" to boolean for hostname option if key == "hostname" - && let serde_yaml::Value::String(s) = &value { + && let serde_yaml::Value::String(s) = value { if s == "false" { - base.options.insert(key, serde_yaml::Value::Bool(false)); + base.options.insert(key.clone(), serde_yaml::Value::Bool(false)); continue; } else if s == "true" { - base.options.insert(key, serde_yaml::Value::Bool(true)); + base.options.insert(key.clone(), serde_yaml::Value::Bool(true)); continue; } } - base.options.insert(key, value); + base.options.insert(key.clone(), value.clone()); } } @@ -81,21 +81,21 @@ impl HostnameMetaPlugin { } // Override with provided outputs, but only if they're enabled - if let Some(outs) = outputs { + if let Some(outs) = &outputs { for (key, value) in outs { // Only add if the output is enabled match key.as_str() { "hostname" => if hostname_enabled { - final_outputs.insert(key, value); + final_outputs.insert(key.clone(), value.clone()); }, "hostname_full" => if hostname_full_enabled { - final_outputs.insert(key, value); + final_outputs.insert(key.clone(), value.clone()); }, "hostname_short" => if hostname_short_enabled { - final_outputs.insert(key, value); + final_outputs.insert(key.clone(), value.clone()); }, _ => { - final_outputs.insert(key, value); + final_outputs.insert(key.clone(), value.clone()); } } } diff --git a/src/meta_plugin/magic_file.rs b/src/meta_plugin/magic_file.rs index 28d7d0b..e343aa2 100644 --- a/src/meta_plugin/magic_file.rs +++ b/src/meta_plugin/magic_file.rs @@ -1,486 +1,4 @@ #[cfg(feature = "magic")] use magic::{Cookie, CookieFlags}; #[cfg(not(feature = "magic"))] -use std::process::{Command, Stdio, Output}; -#[cfg(not(feature = "magic"))] -use which::which; -use std::io::{self, Write}; -use log::debug; -use serde_yaml; - -use crate::common::PIPESIZE; - -use crate::meta_plugin::{MetaPlugin, MetaPluginType, BaseMetaPlugin, MetaPluginResponse, MetaData, process_metadata_outputs}; - -#[cfg(feature = "magic")] -#[derive(Debug)] -pub struct MagicFileMetaPlugin { - buffer: Vec, - max_buffer_size: usize, - is_finalized: bool, - cookie: Option, - base: BaseMetaPlugin, -} - -#[cfg(feature = "magic")] -impl MagicFileMetaPlugin { - pub fn new( - options: Option>, - outputs: Option>, - ) -> MagicFileMetaPlugin { - // Start with default options - let mut final_options = std::collections::HashMap::new(); - final_options.insert("max_buffer_size".to_string(), serde_yaml::Value::Number(PIPESIZE.into())); - if let Some(opts) = options { - for (key, value) in opts { - final_options.insert(key, value); - } - } - - // Start with default outputs - let mut final_outputs = std::collections::HashMap::new(); - let default_outputs = vec!["mime_type".to_string(), "mime_encoding".to_string(), "file_type".to_string()]; - for output_name in default_outputs { - final_outputs.insert(output_name.clone(), serde_yaml::Value::String(output_name)); - } - if let Some(outs) = outputs { - for (key, value) in outs { - final_outputs.insert(key, value); - } - } - - let max_buffer_size = final_options.get("max_buffer_size") - .and_then(|v| v.as_u64()) - .unwrap_or(PIPESIZE as u64) as usize; - - // Ensure the default max_buffer_size is in the options - if !final_options.contains_key("max_buffer_size") { - final_options.insert("max_buffer_size".to_string(), serde_yaml::Value::Number(PIPESIZE.into())); - } - - let mut base = BaseMetaPlugin::new(); - base.outputs = final_outputs; - base.options = final_options; - - MagicFileMetaPlugin { - buffer: Vec::new(), - max_buffer_size, - is_finalized: false, - cookie: None, - base, - } - } - - fn get_magic_result(&self, flags: CookieFlags) -> io::Result { - if self.buffer.is_empty() { - return Ok("empty".to_string()); - } - - let cookie = if let Some(c) = &self.cookie { - c.set_flags(flags) - .map_err(|e| io::Error::new(io::ErrorKind::Other, format!("Failed to set magic flags: {}", e)))?; - c - } else { - return Err(io::Error::new(io::ErrorKind::Other, "Magic cookie not initialized")); - }; - - let result = cookie.buffer(&self.buffer) - .map_err(|e| io::Error::new(io::ErrorKind::Other, format!("Failed to analyze buffer: {}", e)))?; - - // Clean up the result - remove extra whitespace and take first part if needed - let trimmed = result.trim(); - - // For some magic results, we might want just the first part before semicolon or comma - let cleaned = if trimmed.contains(';') { - trimmed.split(';').next().unwrap_or(trimmed).trim().to_string() - } else if trimmed.contains(',') && flags.contains(CookieFlags::MIME_TYPE | CookieFlags::MIME_ENCODING) { - trimmed.split(',').next().unwrap_or(trimmed).trim().to_string() - } else { - trimmed.to_string() - }; - - Ok(cleaned) - } -} - -#[cfg(feature = "magic")] -impl MetaPlugin for MagicFileMetaPlugin { - fn meta_type(&self) -> MetaPluginType { - MetaPluginType::MagicFile - } - - fn is_supported(&self) -> bool { - true - } - - fn is_internal(&self) -> bool { - true - } - - fn is_finalized(&self) -> bool { - self.is_finalized - } - - fn initialize(&mut self) -> MetaPluginResponse { - self.is_finalized = false; - MetaPluginResponse::default() - } - - fn update(&mut self, data: &[u8]) -> MetaPluginResponse { - if self.buffer.len() + data.len() > self.max_buffer_size { - // Truncate to max size, keeping the beginning - let additional_space = self.max_buffer_size.saturating_sub(self.buffer.len()); - if additional_space > 0 { - self.buffer.extend_from_slice(&data[..additional_space.min(data.len())]); - } - } else { - self.buffer.extend_from_slice(data); - } - MetaPluginResponse::default() - } - - fn finalize(&mut self) -> MetaPluginResponse { - let mut metadata = Vec::new(); - let mut response = MetaPluginResponse { - metadata, - is_finalized: true, - }; - - if self.buffer.is_empty() { - self.is_finalized = true; - return response; - } - - // Initialize cookie if not already done - if self.cookie.is_none() { - match Cookie::open(CookieFlags::default()) { - Ok(cookie) => { - self.cookie = Some(cookie); - } - Err(e) => { - debug!("META: Failed to initialize magic cookie: {}", e); - self.is_finalized = true; - return response; - } - } - } - - // Process mime_type - if let Some(_) = self.base.outputs.get("mime_type") { - match self.get_magic_result(CookieFlags::MIME_TYPE) { - Ok(mime_type) => { - if let Some(meta_data) = process_metadata_outputs( - "mime_type", - serde_yaml::Value::String(mime_type), - &self.base.outputs, - ) { - response.metadata.push(meta_data); - } - } - Err(e) => debug!("META: Failed to get MIME type: {}", e), - } - } - - // Process mime_encoding - if let Some(_) = self.base.outputs.get("mime_encoding") { - match self.get_magic_result(CookieFlags::MIME_ENCODING) { - Ok(mime_encoding) => { - if let Some(meta_data) = process_metadata_outputs( - "mime_encoding", - serde_yaml::Value::String(mime_encoding), - &self.base.outputs, - ) { - response.metadata.push(meta_data); - } - } - Err(e) => debug!("META: Failed to get MIME encoding: {}", e), - } - } - - // Process file_type (description) - if let Some(_) = self.base.outputs.get("file_type") { - match self.get_magic_result(CookieFlags::empty()) { - Ok(file_type) => { - if let Some(meta_data) = process_metadata_outputs( - "file_type", - serde_yaml::Value::String(file_type), - &self.base.outputs, - ) { - response.metadata.push(meta_data); - } - } - Err(e) => debug!("META: Failed to get file type: {}", e), - } - } - - self.is_finalized = true; - response - } - - fn outputs(&self) -> &std::collections::HashMap { - &self.base.outputs - } - - fn outputs_mut(&mut self) -> &mut std::collections::HashMap { - &mut self.base.outputs - } - - fn options(&self) -> &std::collections::HashMap { - &self.base.options - } - - fn options_mut(&mut self) -> &mut std::collections::HashMap { - &mut self.base.options - } - - fn default_outputs(&self) -> Vec { - vec!["mime_type".to_string(), "mime_encoding".to_string(), "file_type".to_string()] - } -} - -#[cfg(not(feature = "magic"))] -#[derive(Debug)] -pub struct FallbackMagicFileMetaPlugin { - buffer: Vec, - max_buffer_size: usize, - supported: bool, - is_finalized: bool, - base: BaseMetaPlugin, -} - -#[cfg(not(feature = "magic"))] -impl FallbackMagicFileMetaPlugin { - pub fn new( - options: Option>, - outputs: Option>, - ) -> FallbackMagicFileMetaPlugin { - let supported = which("file").is_ok(); - - // Start with default options - let mut final_options = std::collections::HashMap::new(); - final_options.insert("max_buffer_size".to_string(), serde_yaml::Value::Number(PIPESIZE.into())); - if let Some(opts) = options { - for (key, value) in opts { - final_options.insert(key, value); - } - } - - // Start with default outputs - let mut final_outputs = std::collections::HashMap::new(); - let default_outputs = vec!["mime_type".to_string(), "mime_encoding".to_string(), "file_type".to_string()]; - for output_name in default_outputs { - final_outputs.insert(output_name.clone(), serde_yaml::Value::String(output_name)); - } - if let Some(outs) = outputs { - for (key, value) in outs { - final_outputs.insert(key, value); - } - } - - let max_buffer_size = final_options.get("max_buffer_size") - .and_then(|v| v.as_u64()) - .unwrap_or(PIPESIZE as u64) as usize; - - let mut base = BaseMetaPlugin::new(); - base.outputs = final_outputs; - base.options = final_options; - - FallbackMagicFileMetaPlugin { - buffer: Vec::new(), - max_buffer_size, - supported, - is_finalized: false, - base, - } - } - - fn run_file_command(&self, args: &[&str]) -> io::Result { - if self.buffer.is_empty() { - return Ok("empty".to_string()); - } - - let mut cmd = Command::new("file"); - for arg in args { - cmd.arg(arg); - } - cmd.arg("-").stdin(Stdio::piped()).stdout(Stdio::piped()).stderr(Stdio::piped()); - - let mut child = cmd.spawn() - .map_err(|e| io::Error::new(io::ErrorKind::Other, format!("Failed to spawn file command: {}", e)))?; - - { - let stdin = child.stdin.as_mut().unwrap(); - stdin.write_all(&self.buffer) - .map_err(|e| io::Error::new(io::ErrorKind::Other, format!("Failed to write to file stdin: {}", e)))?; - } - - let output = child.wait_with_output() - .map_err(|e| io::Error::new(io::ErrorKind::Other, format!("Failed to wait on file command: {}", e)))?; - - if !output.status.success() { - let stderr = String::from_utf8_lossy(&output.stderr); - return Err(io::Error::new(io::ErrorKind::Other, format!("File command failed: {}", stderr))); - } - - let stdout = String::from_utf8_lossy(&output.stdout).trim().to_string(); - Ok(stdout) - } - - fn get_mime_info(&self) -> io::Result<(String, String)> { - let mime_output = self.run_file_command(&["-b", "--mime"])?; - if mime_output == "empty" { - return Ok(("application/octet-stream".to_string(), "binary".to_string())); - } - - let parts: Vec<&str> = mime_output.split(';').collect(); - let mime_type = if parts.is_empty() { - mime_output - } else { - parts[0].trim() - }.to_string(); - - let mime_encoding = if parts.len() > 1 { - parts[1].replace(" charset=", "").trim().to_string() - } else { - "binary".to_string() - }; - - Ok((mime_type, mime_encoding)) - } -} - -#[cfg(not(feature = "magic"))] -impl MetaPlugin for FallbackMagicFileMetaPlugin { - fn meta_type(&self) -> MetaPluginType { - MetaPluginType::MagicFile - } - - fn is_supported(&self) -> bool { - self.supported - } - - fn is_internal(&self) -> bool { - true - } - - fn is_finalized(&self) -> bool { - self.is_finalized - } - - fn initialize(&mut self) -> MetaPluginResponse { - self.is_finalized = false; - MetaPluginResponse::default() - } - - fn update(&mut self, data: &[u8]) -> MetaPluginResponse { - if self.buffer.len() + data.len() > self.max_buffer_size { - // Truncate to max size, keeping the beginning - let additional_space = self.max_buffer_size.saturating_sub(self.buffer.len()); - if additional_space > 0 { - self.buffer.extend_from_slice(&data[..additional_space.min(data.len())]); - } - } else { - self.buffer.extend_from_slice(data); - } - MetaPluginResponse::default() - } - - fn finalize(&mut self) -> MetaPluginResponse { - let mut metadata = Vec::new(); - let mut response = MetaPluginResponse { - metadata, - is_finalized: true, - }; - - if !self.supported || self.buffer.is_empty() { - self.is_finalized = true; - return response; - } - - // Process mime_type and mime_encoding from single mime command - match self.get_mime_info() { - Ok((mime_type, mime_encoding)) => { - if let Some(_) = self.base.outputs.get("mime_type") { - if let Some(meta_data) = process_metadata_outputs( - "mime_type", - serde_yaml::Value::String(mime_type.clone()), - &self.base.outputs, - ) { - response.metadata.push(meta_data); - } - } - - if let Some(_) = self.base.outputs.get("mime_encoding") { - if let Some(meta_data) = process_metadata_outputs( - "mime_encoding", - serde_yaml::Value::String(mime_encoding), - &self.base.outputs, - ) { - response.metadata.push(meta_data); - } - } - } - Err(e) => debug!("META: Failed to get MIME info with file command: {}", e), - } - - // Process file_type (description) - if let Some(_) = self.base.outputs.get("file_type") { - match self.run_file_command(&["-b"]) { - Ok(file_type) => { - if let Some(meta_data) = process_metadata_outputs( - "file_type", - serde_yaml::Value::String(file_type), - &self.base.outputs, - ) { - response.metadata.push(meta_data); - } - } - Err(e) => debug!("META: Failed to get file type with file command: {}", e), - } - } - - self.is_finalized = true; - response - } - - fn outputs(&self) -> &std::collections::HashMap { - &self.base.outputs - } - - fn outputs_mut(&mut self) -> &mut std::collections::HashMap { - &mut self.base.outputs - } - - fn options(&self) -> &std::collections::HashMap { - &self.base.options - } - - fn options_mut(&mut self) -> &mut std::collections::HashMap { - &mut self.base.options - } - - fn default_outputs(&self) -> Vec { - vec!["mime_type".to_string(), "mime_encoding".to_string(), "file_type".to_string()] - } -} - -// Registration -#[cfg(feature = "magic")] -use crate::meta_plugin::{register_meta_plugin, MetaPluginType}; -#[cfg(feature = "magic")] -#[ctor::ctor] -fn register_magic_plugin() { - register_meta_plugin(MetaPluginType::MagicFile, |options, outputs| { - Box::new(MagicFileMetaPlugin::new(options, outputs)) - }); -} - -#[cfg(not(feature = "magic"))] -use crate::meta_plugin::register_meta_plugin; -#[cfg(not(feature = "magic"))] -#[ctor::ctor] -fn register_fallback_magic_plugin() { - register_meta_plugin(MetaPluginType::MagicFile, |options, outputs| { - Box::new(FallbackMagicFileMetaPlugin::new(options, outputs)) - }); -} +use std::process::{Command \ No newline at end of file diff --git a/src/meta_plugin/mod.rs b/src/meta_plugin/mod.rs index 62f31a1..91ee8cf 100644 --- a/src/meta_plugin/mod.rs +++ b/src/meta_plugin/mod.rs @@ -17,6 +17,7 @@ pub mod shell; pub mod shell_pid; pub mod keep_pid; pub mod env; +pub mod text; // pub mod text; // Removed duplicate #[cfg(feature = "magic")] @@ -124,8 +125,8 @@ impl BaseMetaPlugin { pub fn initialize_plugin( &mut self, default_outputs: &[&str], - options: Option>, - outputs: Option>, + options: &Option>, + outputs: &Option>, ) { // Set default outputs for output_name in default_outputs { @@ -134,10 +135,14 @@ impl BaseMetaPlugin { // Apply provided options and outputs if let Some(opts) = options { - self.options.extend(opts); + for (key, value) in opts { + self.options.insert(key.clone(), value.clone()); + } } if let Some(outs) = outputs { - self.outputs.extend(outs); + for (key, value) in outs { + self.outputs.insert(key.clone(), value.clone()); + } } } } @@ -457,42 +462,6 @@ pub fn get_meta_plugin( return constructor(options, outputs); } - // Fallback for exec plugin which needs special handling - if meta_plugin_type == MetaPluginType::Exec { - // For exec type, we need to parse the command from options - let mut program_name = String::new(); - let mut args = Vec::new(); - let mut meta_name = MetaPluginType::Exec.to_string(); - let mut split_whitespace = true; - - if let Some(opts) = &options { - if let Some(command_value) = opts.get("command") - && let Some(command_str) = command_value.as_str() { - let parts: Vec<&str> = command_str.split_whitespace().collect(); - if !parts.is_empty() { - program_name = parts[0].to_string(); - args = parts[1..].iter().map(|s| s.to_string()).collect(); - } - } - // Handle other options if needed - if let Some(split_value) = opts.get("split_whitespace") - && let Some(split_bool) = split_value.as_bool() { - split_whitespace = split_bool; - } - if let Some(name_value) = opts.get("name") - && let Some(name_str) = name_value.as_str() { - meta_name = name_str.to_string(); - } - } - - return Box::new(MetaPluginExec::new(&program_name, - args.iter().map(|s| s.as_str()).collect(), - meta_name, - split_whitespace, - options, - outputs)); - } - // Fallback for unknown plugins panic!("Meta plugin {:?} not registered", meta_plugin_type); }