use anyhow::Result;
use cargo_metadata::MetadataCommand;
use std::{cell::RefCell, collections::HashMap, path::PathBuf};
use tokio::process::Command;
use crate::llm::tools::{ResourceType, ToolError};
type PackageId = String;
#[derive(Debug, Clone, PartialEq)]
pub struct Package {
pub id: PackageId,
pub name: String,
pub source: Option<PathBuf>,
pub version: String,
}
#[derive(Debug, Clone, PartialEq)]
pub struct PackageResolver {
pub active_pacmans: Vec<String>,
pub packages: RefCell<HashMap<PackageId, Package>>,
pub refresh_deps: RefCell<bool>,
pub workspace_dir: PathBuf,
}
impl PackageResolver {
pub async fn new(workspace_dir: PathBuf) -> PackageResolver {
let cargo_success = Command::new("cargo")
.args(["metadata", "--no-deps", "--format-version", "1"])
.current_dir(&workspace_dir)
.output()
.await
.map_or_else(
|err| {
false
},
|val| val.status.success(),
);
if cargo_success {
PackageResolver {
active_pacmans: vec!["cargo".to_string()],
packages: RefCell::new(HashMap::new()),
refresh_deps: RefCell::new(true),
workspace_dir,
}
} else {
PackageResolver {
active_pacmans: Vec::new(),
packages: RefCell::new(HashMap::new()),
refresh_deps: RefCell::new(true),
workspace_dir,
}
}
}
pub fn base_directory(&self, id: Option<&str>) -> Result<String, ToolError> {
match id {
Some(pckg_id) => {
if pckg_id == "" || pckg_id == "\"\"" || pckg_id == "workspace" {
Ok(self.workspace_dir.to_string_lossy().to_string())
} else {
self.package_source(&pckg_id)
}
}
None => Ok(self.workspace_dir.to_string_lossy().to_string()),
}
}
pub fn package_source(&self, id: &str) -> Result<String, ToolError> {
self.refresh_dependencies();
let packages = self.packages.borrow();
packages
.get(id)
.ok_or(ToolError::InvalidArgs {
argument: id.to_string(),
reason: format!(
"id '{}' does not denote a package. Search packages with find_package().", id,
),
})
.and_then(|pckg| match &pckg.source {
Some(path) => Ok(path.to_string_lossy().to_string()),
None => Err(ToolError::ResourceNotFound {
resource_type: ResourceType::Path,
identifier: id.to_string(),
}),
})
}
pub fn list_pacmans(&self) -> Vec<String> {
self.active_pacmans.clone()
}
pub fn search_dependency(&self, pattern: &str) -> Vec<String> {
self.refresh_dependencies();
let packages = self.packages.borrow();
packages
.iter()
.filter(|pckg| pckg.1.name.contains(pattern))
.map(|pckg| pckg.0.to_owned())
.collect()
}
fn refresh_dependencies(&self) {
let mut refresh_deps = self.refresh_deps.borrow_mut();
let mut packages = self.packages.borrow_mut();
if *refresh_deps {
*refresh_deps = false;
let metadata = MetadataCommand::new()
.current_dir(&self.workspace_dir)
.exec()
.unwrap();
for package in metadata.packages.iter() {
packages.insert(
format!("{}:{}@{}", "cargo", package.name, package.version),
Package {
id: package.id.repr.clone(),
name: package.name.to_string(),
source: package
.manifest_path
.parent()
.map(|p| p.to_path_buf().into_std_path_buf()),
version: package.version.to_string(),
},
);
}
}
}
}