Co-authored-by: aider (openai/andrew/openrouter/deepseek/deepseek-chat-v3.1) <aider@aider.chat>
235 lines
7.5 KiB
Rust
235 lines
7.5 KiB
Rust
use log::*;
|
|
use std::io::Write;
|
|
use std::process::{Command, Stdio, Child};
|
|
use which::which;
|
|
|
|
use crate::meta_plugin::{MetaPlugin, MetaPluginResponse};
|
|
|
|
pub struct MetaPluginProgram {
|
|
pub program: String,
|
|
pub args: Vec<String>,
|
|
pub supported: bool,
|
|
pub meta_name: String,
|
|
pub split_whitespace: bool,
|
|
process: Option<Child>,
|
|
writer: Option<Box<dyn Write>>,
|
|
item_id: Option<i64>,
|
|
result: Option<String>,
|
|
outputs: std::collections::HashMap<String, serde_yaml::Value>,
|
|
options: std::collections::HashMap<String, serde_yaml::Value>,
|
|
}
|
|
|
|
impl std::fmt::Debug for MetaPluginProgram {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
f.debug_struct("MetaPluginProgram")
|
|
.field("program", &self.program)
|
|
.field("args", &self.args)
|
|
.field("supported", &self.supported)
|
|
.field("meta_name", &self.meta_name)
|
|
.field("split_whitespace", &self.split_whitespace)
|
|
.field("process", &self.process)
|
|
.field("writer", &"Box<dyn Write>")
|
|
.field("outputs", &self.outputs)
|
|
.field("options", &self.options)
|
|
.finish()
|
|
}
|
|
}
|
|
|
|
impl MetaPluginProgram {
|
|
pub fn new(
|
|
program: &str,
|
|
args: Vec<&str>,
|
|
meta_name: String,
|
|
split_whitespace: bool,
|
|
_options: Option<std::collections::HashMap<String, serde_yaml::Value>>,
|
|
outputs: Option<std::collections::HashMap<String, serde_yaml::Value>>,
|
|
) -> MetaPluginProgram {
|
|
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);
|
|
}
|
|
}
|
|
|
|
MetaPluginProgram {
|
|
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,
|
|
meta_name,
|
|
split_whitespace,
|
|
process: None,
|
|
writer: None,
|
|
item_id: None,
|
|
result: None,
|
|
outputs: final_outputs,
|
|
options: final_options,
|
|
}
|
|
}
|
|
|
|
pub fn new_simple(program: &str, args: Vec<&str>, meta_name: String, split_whitespace: bool) -> MetaPluginProgram {
|
|
Self::new(program, args, meta_name, split_whitespace, None, None)
|
|
}
|
|
}
|
|
|
|
impl MetaPlugin for MetaPluginProgram {
|
|
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);
|
|
|
|
// Create metadata to be returned - clone before moving into self.result
|
|
let result_clone = result.clone();
|
|
self.result = Some(result_clone);
|
|
metadata.push(crate::meta_plugin::MetaData {
|
|
name: self.meta_name.clone(),
|
|
value: result,
|
|
});
|
|
}
|
|
} 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_name(&self) -> String {
|
|
self.meta_name.clone()
|
|
}
|
|
|
|
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<String, serde_yaml::Value> {
|
|
&self.outputs
|
|
}
|
|
|
|
fn outputs_mut(&mut self) -> &mut std::collections::HashMap<String, serde_yaml::Value> {
|
|
&mut self.outputs
|
|
}
|
|
|
|
fn default_outputs(&self) -> Vec<String> {
|
|
vec![self.meta_name.clone()]
|
|
}
|
|
|
|
fn default_options(&self) -> std::collections::HashMap<String, serde_yaml::Value> {
|
|
std::collections::HashMap::new()
|
|
}
|
|
|
|
fn options(&self) -> &std::collections::HashMap<String, serde_yaml::Value> {
|
|
&self.options
|
|
}
|
|
|
|
fn options_mut(&mut self) -> &mut std::collections::HashMap<String, serde_yaml::Value> {
|
|
&mut self.options
|
|
}
|
|
}
|