use anyhow::{Result, bail};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::Path;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Extension {
pub package: ExtensionPackage,
pub functions: HashMap<String, ExternalFunction>,
#[serde(default)]
pub link: LinkConfig,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExtensionPackage {
pub name: String,
pub version: String,
#[serde(default)]
pub description: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExternalFunction {
pub args: Vec<ArgType>,
pub ret: RetType,
pub impl_name: String,
#[serde(default)]
pub is_property: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum ArgType {
Number,
String,
Boolean,
Any,
Array,
Object,
Function,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum RetType {
Number,
String,
Boolean,
Any,
Array,
Object,
Void,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct LinkConfig {
#[serde(default)]
pub runtime: Option<String>,
#[serde(default)]
pub lib: Option<String>,
#[serde(default)]
pub libs: Vec<String>,
#[serde(default)]
pub flags: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CargoToml {
#[serde(default)]
pub dependencies: HashMap<String, toml::Value>,
}
#[derive(Debug, Clone)]
pub struct ExtensionRegistry {
pub extensions: Vec<Extension>,
pub function_map: HashMap<String, String>,
}
impl ExtensionRegistry {
pub fn new() -> Self {
Self {
extensions: Vec::new(),
function_map: HashMap::new(),
}
}
pub fn register(&mut self, extension: Extension) {
for (ts_name, func) in &extension.functions {
self.function_map.insert(ts_name.clone(), func.impl_name.clone());
}
self.extensions.push(extension);
}
pub fn get_c_name(&self, ts_name: &str) -> Option<&String> {
self.function_map.get(ts_name)
}
pub fn is_external_function(&self, ts_name: &str) -> bool {
self.function_map.contains_key(ts_name)
}
}
pub fn parse_manifest(path: &Path) -> Result<Extension> {
let content = std::fs::read_to_string(path)?;
let manifest: Extension = toml::from_str(&content)?;
Ok(manifest)
}
pub fn parse_cargo_toml(path: &Path) -> Result<CargoToml> {
let content = std::fs::read_to_string(path)?;
let cargo: CargoToml = toml::from_str(&content)?;
Ok(cargo)
}
pub fn find_ts_native_crates(cargo_toml: &CargoToml) -> Vec<String> {
cargo_toml
.dependencies
.keys()
.filter(|name| name.starts_with("ts-native-"))
.cloned()
.collect()
}
pub fn load_extension_from_path(path: &Path) -> Result<Extension> {
let manifest_path = path.join("manifest.toml");
if !manifest_path.exists() {
bail!("manifest.toml not found in {:?}", path);
}
parse_manifest(&manifest_path)
}
pub fn load_extension_from_crate(crate_name: &str) -> Result<Extension> {
let crate_path = find_crate_path(crate_name)?;
load_extension_from_path(&crate_path)
}
fn find_crate_path(crate_name: &str) -> Result<std::path::PathBuf> {
if let Ok(manifest_dir) = std::env::var("CARGO_MANIFEST_DIR") {
let local_path = std::path::PathBuf::from(&manifest_dir)
.join(crate_name);
if local_path.join("manifest.toml").exists() {
return Ok(local_path);
}
}
let current_dir = std::env::current_dir()?;
let local_path = current_dir.join(crate_name);
if local_path.join("manifest.toml").exists() {
return Ok(local_path);
}
let parent_dir = current_dir.parent().unwrap_or(¤t_dir);
let sibling_path = parent_dir.join(crate_name);
if sibling_path.join("manifest.toml").exists() {
return Ok(sibling_path);
}
bail!("Crate {} not found. Tried:\n - ./{}/\n - ../{}/", crate_name, crate_name, crate_name)
}
pub fn discover_extensions_from_cargo(cargo_toml_path: &Path) -> Result<ExtensionRegistry> {
let mut registry = ExtensionRegistry::new();
if !cargo_toml_path.exists() {
return Ok(registry);
}
let cargo_toml = parse_cargo_toml(cargo_toml_path)?;
let ts_native_crates = find_ts_native_crates(&cargo_toml);
for crate_name in ts_native_crates {
match load_extension_from_crate(&crate_name) {
Ok(ext) => {
println!(" 加载扩展包: {} ({})", ext.package.name, crate_name);
registry.register(ext);
}
Err(e) => {
eprintln!(" ⚠️ 加载扩展包 {} 失败: {}", crate_name, e);
}
}
}
Ok(registry)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_manifest() {
let manifest_content = r#"
[package]
name = "stdlib"
version = "0.1.0"
[functions]
"Math.sin" = { args = ["number"], ret = "number", impl_name = "js_math_sin" }
[link]
runtime = "runtime.c"
"#;
let ext: Extension = toml::from_str(manifest_content).unwrap();
assert_eq!(ext.package.name, "stdlib");
assert!(ext.functions.contains_key("Math.sin"));
}
}