From beef2e773e712da1b1f5faa064912c1b8801fcea Mon Sep 17 00:00:00 2001 From: "Andrew Phillips (aider)" Date: Thu, 22 May 2025 09:38:43 -0300 Subject: [PATCH] feat: add meta plugin with file and none implementations --- src/meta_plugin.rs | 80 +++++++++++++++++++++++++++++++++++ src/meta_plugin/none.rs | 43 +++++++++++++++++++ src/meta_plugin/program.rs | 85 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 208 insertions(+) create mode 100644 src/meta_plugin/none.rs diff --git a/src/meta_plugin.rs b/src/meta_plugin.rs index e69de29..d734353 100644 --- a/src/meta_plugin.rs +++ b/src/meta_plugin.rs @@ -0,0 +1,80 @@ +use anyhow::Result; +use std::io; + +use lazy_static::lazy_static; + +extern crate enum_map; +use enum_map::enum_map; +use enum_map::{Enum, EnumMap}; + +pub mod none; +pub mod program; + +use crate::meta_plugin::none::MetaPluginNone; +use crate::meta_plugin::program::MetaPluginProgram; + +use strum::IntoEnumIterator; + +#[derive(Debug, Eq, PartialEq, Clone, strum::EnumIter, strum::Display, strum::EnumString, Enum)] +#[strum(ascii_case_insensitive)] +pub enum MetaPluginType { + File, + None, +} + +pub trait MetaPlugin { + fn is_supported(&self) -> bool { + true + } + fn create(&self) -> Result>; + fn finalize(&mut self) -> io::Result; + + // Update the meta plugin with new data + fn update(&mut self, data: &[u8]); +} + +use std::io::Write; + +// Writer that implements Write for the program meta plugin +struct ProgramWriter { + stdin: std::process::ChildStdin, +} + +impl Write for ProgramWriter { + fn write(&mut self, buf: &[u8]) -> std::io::Result { + self.stdin.write(buf) + } + + fn flush(&mut self) -> std::io::Result<()> { + self.stdin.flush() + } +} + +lazy_static! { + pub static ref META_PLUGIN_PROGRAMS: EnumMap> = enum_map! { + MetaPluginType::File => { + let program = MetaPluginProgram::new("file", vec!["-bE", "-"]); + if program.supported { Some(program) } else { None } + } + MetaPluginType::None => None + }; +} + +pub fn get_meta_plugin(meta_plugin_type: MetaPluginType) -> Box { + match meta_plugin_type { + MetaPluginType::File => Box::new(MetaPluginProgram::new("file", vec!["-bE", "-"])), + MetaPluginType::None => Box::new(MetaPluginNone::new()), + } +} + +pub fn default_meta_plugin_type() -> MetaPluginType { + let mut default = MetaPluginType::None; + for meta_plugin_type in MetaPluginType::iter() { + let meta_plugin = get_meta_plugin(meta_plugin_type.clone()); + if meta_plugin.is_supported() { + default = meta_plugin_type; + break; + } + } + default +} diff --git a/src/meta_plugin/none.rs b/src/meta_plugin/none.rs new file mode 100644 index 0000000..60bb2ec --- /dev/null +++ b/src/meta_plugin/none.rs @@ -0,0 +1,43 @@ +use anyhow::Result; +use log::*; +use std::io::{self, Write}; + +#[derive(Debug, Eq, PartialEq, Clone, Default)] +pub struct MetaPluginNone {} + +impl MetaPluginNone { + pub fn new() -> MetaPluginNone { + MetaPluginNone {} + } +} + +impl MetaPlugin for MetaPluginNone { + fn create(&self) -> Result> { + Ok(Box::new(DummyWriter::new())) + } + + fn finalize(&mut self) -> io::Result { + Ok("none".to_string()) + } + + fn update(&mut self, _data: &[u8]) {} +} + +// Dummy writer that implements Write for the none meta plugin +struct DummyWriter; + +impl DummyWriter { + fn new() -> Self { + DummyWriter + } +} + +impl Write for DummyWriter { + fn write(&mut self, _buf: &[u8]) -> io::Result { + Ok(0) + } + + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} diff --git a/src/meta_plugin/program.rs b/src/meta_plugin/program.rs index e69de29..6fb51c5 100644 --- a/src/meta_plugin/program.rs +++ b/src/meta_plugin/program.rs @@ -0,0 +1,85 @@ +use crate::meta_plugin::MetaPlugin; +use anyhow::{Context, Result, anyhow}; +use log::*; +use std::env; +use std::fs; +use std::io; +use std::io::Write; +use std::os::unix::fs::PermissionsExt; +use std::process::{Command, Stdio}; + +#[derive(Clone, Debug)] +pub struct MetaPluginProgram { + pub program: String, + pub args: Vec, + pub supported: bool, +} + +impl MetaPluginProgram { + pub fn new(program: &str, args: Vec<&str>) -> MetaPluginProgram { + let program_path = get_program_path(program); + let supported = program_path.is_ok(); + + MetaPluginProgram { + program: program_path.unwrap_or(program.to_string()), + args: args.iter().map(|s| s.to_string()).collect(), + supported, + } + } +} + +impl MetaPlugin for MetaPluginProgram { + fn is_supported(&self) -> bool { + self.supported + } + + fn create(&self) -> Result> { + debug!("META: Writing using {:?}", *self); + + let program = self.program.clone(); + let args = self.args.clone(); + + debug!("META: Executing command: {:?} {:?}", program, args); + + let mut process = Command::new(program.clone()) + .args(args.clone()) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .spawn() + .context(anyhow!( + "Problem spawning child process: {:?} {:?}", + program, + args + ))?; + + Ok(Box::new(ProgramWriter { + stdin: process.stdin.take().unwrap(), + })) + } + + fn finalize(&mut self) -> io::Result { + Ok("program".to_string()) + } + + fn update(&mut self, _data: &[u8]) { + // This is handled by the ProgramWriter implementation + } +} + +fn get_program_path(program: &str) -> Result { + debug!("META: Looking for executable: {}", program); + if let Ok(path) = env::var("PATH") { + for p in path.split(':') { + let p_str = format!("{}/{}", p, program); + let stat = fs::metadata(p_str.clone()); + if let Ok(stat) = stat { + let md = stat; + let permissions = md.permissions(); + if md.is_file() && permissions.mode() & 0o111 != 0 { + return Ok(p_str); + } + } + } + } + Err(anyhow!("Unable to find binary {} in PATH", program)) +}