use crate::models::{EnvVarDefinition, ParameterDefinition, ParameterType, ToolDefinition};
use anyhow::Result;
use regex::Regex;
use std::collections::HashMap;
pub fn parse(source: &str) -> Result<ToolDefinition> {
let jsdoc_re = Regex::new(r"/\*\*([\s\S]*?)\*/")?;
let jsdoc = jsdoc_re
.captures(source)
.ok_or_else(|| anyhow::anyhow!("No JSDoc comment found"))?
.get(1)
.unwrap()
.as_str();
let mut description = String::new();
let mut parameters = Vec::new();
let mut env_vars = Vec::new();
let mut required_tools = Vec::new();
let metadata = HashMap::new();
let property_re =
Regex::new(r"@property\s+\{([^}]+)\}\s+(\[?)([a-zA-Z0-9_]+)\]?\s*-?\s*(.*)$")?;
let env_re = Regex::new(r"@env\s+\{([A-Z_][A-Z0-9_]*)\}\s*(.*)")?;
let meta_re = Regex::new(r"@meta\s+require-tools\s+(.+)$")?;
for line in jsdoc.lines() {
let line = line.trim().trim_start_matches('*').trim();
if line.is_empty() || line.starts_with('@') {
if description.is_empty() && !line.starts_with('@') {
continue;
}
} else if description.is_empty() {
description = line.to_string();
}
if let Some(caps) = property_re.captures(line) {
let type_str = caps.get(1).unwrap().as_str();
let optional_bracket = caps.get(2).unwrap().as_str();
let name = caps.get(3).unwrap().as_str().to_string();
let desc = caps.get(4).unwrap().as_str().to_string();
let required = optional_bracket.is_empty();
let param_type = parse_js_type(type_str)?;
parameters.push(ParameterDefinition {
name,
param_type,
description: desc,
required,
default: None,
enum_values: None,
});
} else if let Some(caps) = env_re.captures(line) {
let name = caps.get(1).unwrap().as_str().to_string();
let desc = caps.get(2).unwrap().as_str().to_string();
let required = !desc.contains("[optional]");
env_vars.push(EnvVarDefinition {
name,
description: desc.replace("[optional]", "").trim().to_string(),
required,
default: None,
});
} else if let Some(caps) = meta_re.captures(line) {
required_tools = caps
.get(1)
.unwrap()
.as_str()
.split_whitespace()
.map(|s| s.to_string())
.collect();
}
}
if description.is_empty() {
anyhow::bail!("No description found in JSDoc");
}
Ok(ToolDefinition {
description,
parameters,
env_vars,
required_tools,
metadata,
})
}
fn parse_js_type(type_str: &str) -> Result<ParameterType> {
let type_str = type_str.trim();
if type_str.contains('|') {
let values: Vec<String> = type_str
.split('|')
.map(|s| s.trim().trim_matches('\'').trim_matches('"').to_string())
.collect();
return Ok(ParameterType::Enum(values));
}
if type_str.ends_with("[]") {
return Ok(ParameterType::Array);
}
match type_str.to_lowercase().as_str() {
"string" => Ok(ParameterType::String),
"number" => Ok(ParameterType::Number),
"integer" => Ok(ParameterType::Integer),
"boolean" => Ok(ParameterType::Boolean),
"array" => Ok(ParameterType::Array),
"object" => Ok(ParameterType::Object),
_ => Ok(ParameterType::String),
}
}