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 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 describe_re = Regex::new(r"^#\s*@describe\s+(.+)$")?;
let option_re =
Regex::new(r"^#\s*@option\s+--([a-z0-9-]+)(!?)(?:\[([^\]]+)\])?(?:<([^>]+)>)?\s+(.*)$")?;
let flag_re = Regex::new(r"^#\s*@flag\s+--([a-z0-9-]+)\s+(.*)$")?;
let env_re = Regex::new(r"^#\s*@env\s+([A-Z0-9_]+)(!?)(?:=([^\s]+))?\s+(.*)$")?;
let meta_re = Regex::new(r"^#\s*@meta\s+require-tools\s+(.+)$")?;
for line in source.lines() {
let line = line.trim();
if let Some(caps) = describe_re.captures(line) {
description = caps.get(1).unwrap().as_str().to_string();
} else if let Some(caps) = option_re.captures(line) {
let name = caps.get(1).unwrap().as_str().replace('-', "_");
let required = !caps.get(2).unwrap().as_str().is_empty();
let enum_values = caps.get(3).map(|m| {
m.as_str()
.split('|')
.map(|s| s.to_string())
.collect::<Vec<_>>()
});
let type_hint = caps.get(4).map(|m| m.as_str());
let desc = caps.get(5).unwrap().as_str().to_string();
let (param_type, enum_values_for_def) = if let Some(values) = enum_values {
(ParameterType::Enum(values.clone()), Some(values))
} else if let Some(hint) = type_hint {
let p_type = match hint.to_uppercase().as_str() {
"INT" => ParameterType::Integer,
"NUM" => ParameterType::Number,
_ => ParameterType::String,
};
(p_type, None)
} else {
(ParameterType::String, None)
};
parameters.push(ParameterDefinition {
name,
param_type,
description: desc,
required,
default: None,
enum_values: enum_values_for_def,
});
} else if let Some(caps) = flag_re.captures(line) {
let name = caps.get(1).unwrap().as_str().replace('-', "_");
let desc = caps.get(2).unwrap().as_str().to_string();
parameters.push(ParameterDefinition {
name,
param_type: ParameterType::Boolean,
description: desc,
required: false,
default: Some(serde_json::Value::Bool(false)),
enum_values: None,
});
} else if let Some(caps) = env_re.captures(line) {
let name = caps.get(1).unwrap().as_str().to_string();
let required = !caps.get(2).unwrap().as_str().is_empty();
let default = caps.get(3).map(|m| m.as_str().to_string());
let desc = caps.get(4).unwrap().as_str().to_string();
env_vars.push(EnvVarDefinition {
name,
description: desc,
required,
default,
});
} 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 @describe annotation found in bash script. A description is required.");
}
Ok(ToolDefinition {
description,
parameters,
env_vars,
required_tools,
metadata,
})
}