feat: add meta plugin with file and none implementations

This commit is contained in:
Andrew Phillips (aider)
2025-05-22 09:38:43 -03:00
parent 1fd5ec1988
commit beef2e773e
3 changed files with 208 additions and 0 deletions

View File

@@ -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<Box<dyn Write>>;
fn finalize(&mut self) -> io::Result<String>;
// 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<usize> {
self.stdin.write(buf)
}
fn flush(&mut self) -> std::io::Result<()> {
self.stdin.flush()
}
}
lazy_static! {
pub static ref META_PLUGIN_PROGRAMS: EnumMap<MetaPluginType, Option<MetaPluginProgram>> = 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<dyn MetaPlugin> {
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
}

43
src/meta_plugin/none.rs Normal file
View File

@@ -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<Box<dyn Write>> {
Ok(Box::new(DummyWriter::new()))
}
fn finalize(&mut self) -> io::Result<String> {
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<usize> {
Ok(0)
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}

View File

@@ -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<String>,
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<Box<dyn Write>> {
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<String> {
Ok("program".to_string())
}
fn update(&mut self, _data: &[u8]) {
// This is handled by the ProgramWriter implementation
}
}
fn get_program_path(program: &str) -> Result<String> {
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))
}