use std::collections::HashSet;
use std::path::{Path, PathBuf};
use crate::loc::counter::{LineKind, classify_reader};
use crate::loc::language::languages;
use crate::string_mask::multi_line_string_mask;
pub fn extract(source: &str) -> Vec<String> {
let Some(spec) = languages().iter().find(|s| s.name == "Kaikai") else {
return vec![];
};
let normalized = source.replace("\r\n", "\n");
let lines: Vec<String> = normalized.lines().map(String::from).collect();
let kinds = classify_reader(normalized.as_bytes(), spec);
let in_string = multi_line_string_mask(&lines, spec);
lines
.iter()
.zip(kinds)
.zip(in_string)
.filter(|((_, kind), masked)| *kind == LineKind::Code && !masked)
.filter_map(|((line, _), _)| import_path(line))
.map(str::to_string)
.collect()
}
fn import_path(line: &str) -> Option<&str> {
let rest = line.trim_start().strip_prefix("import")?;
if !rest.starts_with(char::is_whitespace) {
return None;
}
let rest = rest.trim_start();
let end = rest
.find(|c: char| !(c.is_alphanumeric() || c == '_' || c == '.'))
.unwrap_or(rest.len());
let path = rest[..end].trim_end_matches('.');
path.split('.').all(|seg| !seg.is_empty()).then_some(path)
}
pub fn resolve(importer: &Path, module: &str, file_set: &HashSet<PathBuf>) -> Vec<PathBuf> {
let dir = importer.parent().unwrap_or(Path::new(""));
let rel = module.replace('.', "/");
for root in dir.ancestors() {
let as_file = root.join(format!("{rel}.kai"));
if file_set.contains(&as_file) {
return vec![as_file];
}
}
for root in dir.ancestors() {
let package = root.join(&rel);
if !file_set.contains(&package.join("kai.toml")) {
continue;
}
let mut members: Vec<PathBuf> = file_set
.iter()
.filter(|p| {
p.parent() == Some(package.as_path())
&& p.extension().is_some_and(|e| e == "kai")
&& p.as_path() != importer
})
.cloned()
.collect();
if !members.is_empty() {
members.sort();
return members;
}
}
Vec::new()
}
#[cfg(test)]
#[path = "kaikai_test.rs"]
mod tests;