use std::path::Path;
use super::kaikai;
pub fn is_supported(language: &str) -> bool {
matches!(
language,
"Rust"
| "Python"
| "JavaScript"
| "TypeScript"
| "JSX"
| "TSX"
| "Go"
| "Kaikai"
| "Elixir"
| "Elixir Script"
)
}
pub fn extract_imports(path: &Path, language: &str, source: &str) -> Vec<String> {
match language {
"Rust" => extract_rust(path, source),
"Python" => extract_python(source),
"JavaScript" | "TypeScript" | "JSX" | "TSX" => extract_js(source),
"Go" => extract_go(source),
"Kaikai" => kaikai::extract(source),
_ => vec![],
}
}
fn extract_rust(_path: &Path, source: &str) -> Vec<String> {
let mut imports = Vec::new();
for line in source.lines() {
let trimmed = line.trim();
if !trimmed.ends_with(';') || trimmed.contains('{') {
continue;
}
if let Some(rest) = strip_visibility(trimmed).strip_prefix("mod ") {
let name = rest.trim_end_matches(';').trim();
if !name.is_empty() && name.chars().all(|c| c.is_alphanumeric() || c == '_') {
imports.push(name.to_string());
}
}
}
imports
}
fn strip_visibility(decl: &str) -> &str {
let Some(rest) = decl.strip_prefix("pub") else {
return decl;
};
let rest = match rest.strip_prefix('(') {
Some(scoped) => scoped.split_once(')').map_or(rest, |(_, after)| after),
None => rest,
};
if rest.starts_with(char::is_whitespace) {
rest.trim_start()
} else {
decl
}
}
fn extract_python(source: &str) -> Vec<String> {
let mut imports = Vec::new();
for line in source.lines() {
let trimmed = line.trim();
if let Some(module) = trimmed
.strip_prefix("from ")
.and_then(|r| r.split_whitespace().next())
{
if module.starts_with('.') && module != "." {
imports.push(module.to_string());
}
}
}
imports
}
fn extract_js(source: &str) -> Vec<String> {
let mut imports = Vec::new();
for line in source.lines() {
let trimmed = line.trim();
if trimmed.starts_with("//") || trimmed.starts_with("*") || trimmed.starts_with("/*") {
continue;
}
if let Some(path) = find_relative_string(trimmed) {
imports.push(path);
}
}
imports
}
fn find_relative_string(line: &str) -> Option<String> {
for &q in &['"', '\'', '`'] {
let mut search = line;
while let Some(start) = search.find(q) {
let inner = &search[start + 1..];
if (inner.starts_with("./") || inner.starts_with("../"))
&& let Some(end) = inner.find(q)
{
return Some(inner[..end].to_string());
}
search = &search[start + 1..];
if search.is_empty() {
break;
}
}
}
None
}
fn extract_go(source: &str) -> Vec<String> {
let mut imports = Vec::new();
let mut in_block = false;
for line in source.lines() {
let trimmed = line.trim();
if trimmed == "import (" {
in_block = true;
continue;
}
if in_block && trimmed == ")" {
in_block = false;
continue;
}
let candidate = if in_block {
Some(trimmed)
} else {
trimmed.strip_prefix("import ").map(str::trim)
};
if let Some(s) = candidate {
if let Some(path) = extract_quoted(s).filter(|p| !p.is_empty()) {
imports.push(path);
}
}
}
imports
}
fn extract_quoted(s: &str) -> Option<String> {
let start = s.rfind('"')?;
let before = &s[..start];
let start2 = before.rfind('"')? + 1;
Some(before[start2..].to_string())
}
#[cfg(test)]
#[path = "extractor_test.rs"]
mod tests;