use anyhow::{Context, Result};
use std::path::{Path, PathBuf};
use std::process::Command;
pub fn check_cargo() -> Result<()> {
Command::new("cargo")
.arg("--version")
.output()
.context("Failed to execute cargo. Is it installed and in PATH?")?;
Ok(())
}
pub fn check_wasm_target() -> Result<bool> {
let output = Command::new("rustup")
.args(["target", "list", "--installed"])
.output()
.context("Failed to check installed Rust targets")?;
let output_str = String::from_utf8_lossy(&output.stdout);
Ok(output_str.contains("wasm32-wasi"))
}
pub fn install_wasm_target() -> Result<()> {
println!("Installing wasm32-wasi target...");
let status = Command::new("rustup")
.args(["target", "add", "wasm32-wasi"])
.status()
.context("Failed to install wasm32-wasi target")?;
if !status.success() {
anyhow::bail!("Failed to install wasm32-wasi target");
}
Ok(())
}
pub fn find_manifest(dir: &Path) -> Result<PathBuf> {
let manifest_path = dir.join("plugin.yaml");
if manifest_path.exists() {
return Ok(manifest_path);
}
let manifest_path = dir.join("plugin.yml");
if manifest_path.exists() {
return Ok(manifest_path);
}
anyhow::bail!("No plugin.yaml or plugin.yml found in {}", dir.display())
}
pub fn find_cargo_toml(dir: &Path) -> Result<PathBuf> {
let cargo_path = dir.join("Cargo.toml");
if cargo_path.exists() {
return Ok(cargo_path);
}
anyhow::bail!("No Cargo.toml found in {}", dir.display())
}
pub fn get_wasm_output_path(project_dir: &Path, release: bool) -> Result<PathBuf> {
let profile = if release { "release" } else { "debug" };
let target_dir = project_dir.join("target").join("wasm32-wasi").join(profile);
Ok(target_dir)
}
pub fn read_manifest(path: &Path) -> Result<serde_yaml::Value> {
let content = std::fs::read_to_string(path)
.with_context(|| format!("Failed to read manifest at {}", path.display()))?;
let manifest: serde_yaml::Value = serde_yaml::from_str(&content)
.with_context(|| format!("Failed to parse manifest at {}", path.display()))?;
Ok(manifest)
}
pub fn get_plugin_id(manifest: &serde_yaml::Value) -> Result<String> {
manifest
.get("id")
.and_then(|v| v.as_str())
.map(String::from)
.context("Plugin manifest must have an 'id' field")
}
pub fn get_plugin_version(manifest: &serde_yaml::Value) -> Result<String> {
manifest
.get("version")
.and_then(|v| v.as_str())
.map(String::from)
.context("Plugin manifest must have a 'version' field")
}
pub fn current_dir() -> Result<PathBuf> {
std::env::current_dir().context("Failed to get current directory")
}
pub fn ensure_dir(path: &Path) -> Result<()> {
if !path.exists() {
std::fs::create_dir_all(path)
.with_context(|| format!("Failed to create directory {}", path.display()))?;
}
Ok(())
}
#[allow(dead_code)]
pub fn to_rust_identifier(s: &str) -> String {
s.replace(['-', ' '], "_").to_lowercase()
}
pub fn to_kebab_case(s: &str) -> String {
s.replace(['_', ' '], "-").to_lowercase()
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
use tempfile::TempDir;
#[test]
fn test_to_rust_identifier_hyphen() {
assert_eq!(to_rust_identifier("my-plugin"), "my_plugin");
}
#[test]
fn test_to_rust_identifier_space() {
assert_eq!(to_rust_identifier("my plugin"), "my_plugin");
}
#[test]
fn test_to_rust_identifier_uppercase() {
assert_eq!(to_rust_identifier("My-Plugin"), "my_plugin");
}
#[test]
fn test_to_rust_identifier_mixed() {
assert_eq!(to_rust_identifier("My Plugin-Name"), "my_plugin_name");
}
#[test]
fn test_to_rust_identifier_already_valid() {
assert_eq!(to_rust_identifier("my_plugin"), "my_plugin");
}
#[test]
fn test_to_kebab_case_underscore() {
assert_eq!(to_kebab_case("my_plugin"), "my-plugin");
}
#[test]
fn test_to_kebab_case_space() {
assert_eq!(to_kebab_case("my plugin"), "my-plugin");
}
#[test]
fn test_to_kebab_case_uppercase() {
assert_eq!(to_kebab_case("My_Plugin"), "my-plugin");
}
#[test]
fn test_to_kebab_case_mixed() {
assert_eq!(to_kebab_case("My Plugin_Name"), "my-plugin-name");
}
#[test]
fn test_to_kebab_case_already_valid() {
assert_eq!(to_kebab_case("my-plugin"), "my-plugin");
}
#[test]
fn test_find_manifest_yaml() {
let temp_dir = TempDir::new().unwrap();
let manifest_path = temp_dir.path().join("plugin.yaml");
fs::write(&manifest_path, "id: test-plugin\nversion: 1.0.0").unwrap();
let result = find_manifest(temp_dir.path());
assert!(result.is_ok());
assert_eq!(result.unwrap(), manifest_path);
}
#[test]
fn test_find_manifest_yml() {
let temp_dir = TempDir::new().unwrap();
let manifest_path = temp_dir.path().join("plugin.yml");
fs::write(&manifest_path, "id: test-plugin\nversion: 1.0.0").unwrap();
let result = find_manifest(temp_dir.path());
assert!(result.is_ok());
assert_eq!(result.unwrap(), manifest_path);
}
#[test]
fn test_find_manifest_not_found() {
let temp_dir = TempDir::new().unwrap();
let result = find_manifest(temp_dir.path());
assert!(result.is_err());
}
#[test]
fn test_find_manifest_prefers_yaml() {
let temp_dir = TempDir::new().unwrap();
let yaml_path = temp_dir.path().join("plugin.yaml");
let yml_path = temp_dir.path().join("plugin.yml");
fs::write(&yaml_path, "id: yaml-plugin").unwrap();
fs::write(&yml_path, "id: yml-plugin").unwrap();
let result = find_manifest(temp_dir.path());
assert!(result.is_ok());
assert_eq!(result.unwrap(), yaml_path);
}
#[test]
fn test_find_cargo_toml_exists() {
let temp_dir = TempDir::new().unwrap();
let cargo_path = temp_dir.path().join("Cargo.toml");
fs::write(&cargo_path, "[package]\nname = \"test\"").unwrap();
let result = find_cargo_toml(temp_dir.path());
assert!(result.is_ok());
assert_eq!(result.unwrap(), cargo_path);
}
#[test]
fn test_find_cargo_toml_not_found() {
let temp_dir = TempDir::new().unwrap();
let result = find_cargo_toml(temp_dir.path());
assert!(result.is_err());
}
#[test]
fn test_get_wasm_output_path_release() {
let temp_dir = TempDir::new().unwrap();
let result = get_wasm_output_path(temp_dir.path(), true).unwrap();
assert!(result.ends_with("target/wasm32-wasi/release"));
}
#[test]
fn test_get_wasm_output_path_debug() {
let temp_dir = TempDir::new().unwrap();
let result = get_wasm_output_path(temp_dir.path(), false).unwrap();
assert!(result.ends_with("target/wasm32-wasi/debug"));
}
#[test]
fn test_read_manifest_valid() {
let temp_dir = TempDir::new().unwrap();
let manifest_path = temp_dir.path().join("plugin.yaml");
fs::write(&manifest_path, "id: test-plugin\nversion: 1.0.0\nname: Test Plugin").unwrap();
let result = read_manifest(&manifest_path);
assert!(result.is_ok());
let manifest = result.unwrap();
assert_eq!(manifest["id"].as_str(), Some("test-plugin"));
assert_eq!(manifest["version"].as_str(), Some("1.0.0"));
}
#[test]
fn test_read_manifest_not_found() {
let temp_dir = TempDir::new().unwrap();
let manifest_path = temp_dir.path().join("nonexistent.yaml");
let result = read_manifest(&manifest_path);
assert!(result.is_err());
}
#[test]
fn test_read_manifest_invalid_yaml() {
let temp_dir = TempDir::new().unwrap();
let manifest_path = temp_dir.path().join("plugin.yaml");
fs::write(&manifest_path, "this: is: not: valid: yaml: [").unwrap();
let result = read_manifest(&manifest_path);
assert!(result.is_err());
}
#[test]
fn test_get_plugin_id_valid() {
let manifest: serde_yaml::Value =
serde_yaml::from_str("id: my-plugin\nversion: 1.0.0").unwrap();
let result = get_plugin_id(&manifest);
assert!(result.is_ok());
assert_eq!(result.unwrap(), "my-plugin");
}
#[test]
fn test_get_plugin_id_missing() {
let manifest: serde_yaml::Value = serde_yaml::from_str("version: 1.0.0").unwrap();
let result = get_plugin_id(&manifest);
assert!(result.is_err());
}
#[test]
fn test_get_plugin_version_valid() {
let manifest: serde_yaml::Value =
serde_yaml::from_str("id: my-plugin\nversion: 2.1.0").unwrap();
let result = get_plugin_version(&manifest);
assert!(result.is_ok());
assert_eq!(result.unwrap(), "2.1.0");
}
#[test]
fn test_get_plugin_version_missing() {
let manifest: serde_yaml::Value = serde_yaml::from_str("id: my-plugin").unwrap();
let result = get_plugin_version(&manifest);
assert!(result.is_err());
}
#[test]
fn test_ensure_dir_creates_new() {
let temp_dir = TempDir::new().unwrap();
let new_dir = temp_dir.path().join("new/nested/dir");
assert!(!new_dir.exists());
let result = ensure_dir(&new_dir);
assert!(result.is_ok());
assert!(new_dir.exists());
}
#[test]
fn test_ensure_dir_existing() {
let temp_dir = TempDir::new().unwrap();
let result = ensure_dir(temp_dir.path());
assert!(result.is_ok());
}
#[test]
fn test_current_dir() {
let result = current_dir();
assert!(result.is_ok());
assert!(result.unwrap().exists());
}
}