use std::collections::{BTreeMap, BTreeSet, HashMap};
use std::path::Path;
use super::graph::Source;
use super::layout::Layout;
use super::{python, python_names};
struct Module {
label: String,
names: Vec<String>,
}
pub struct Modules(Vec<Module>);
fn label(path: &Path) -> String {
let named = match path.file_stem() {
Some(stem) if stem == "__init__" => path.parent().and_then(Path::file_name),
stem => stem,
};
named.map_or_else(String::new, |name| name.to_string_lossy().into_owned())
}
impl Modules {
pub fn of(sources: &[Source]) -> Self {
let module = |source: &Source| Module {
label: label(&source.path),
names: match source.language.as_str() {
"Python" => python_names::defined(&source.text),
_ => Vec::new(),
},
};
Modules(sources.iter().map(module).collect())
}
pub fn uses(
&self,
source: &Source,
layout: &Layout,
by_path: &HashMap<&Path, usize>,
) -> Option<BTreeMap<usize, BTreeSet<String>>> {
(source.language == "Python").then(|| self.uses_of(source, layout, by_path))
}
fn uses_of(
&self,
source: &Source,
layout: &Layout,
by_path: &HashMap<&Path, usize>,
) -> BTreeMap<usize, BTreeSet<String>> {
let resolve = |import: &str| {
let path = python::resolve(&source.path, import, &layout.known, &layout.python_roots)?;
by_path.get(path.as_path()).copied()
};
let read = python::read(&source.text);
let mut uses: BTreeMap<usize, BTreeSet<String>> = BTreeMap::new();
for binding in &read.bindings {
let submodule = binding.submodule().and_then(|module| resolve(&module));
let Some(to) = submodule.or_else(|| resolve(&binding.module)) else {
continue;
};
let module = &self.0[to];
let taken = module.taken(binding, submodule.is_some(), &read.code);
let calls = taken.iter().map(|name| format!("{}.{name}", module.label));
uses.entry(to).or_default().extend(calls);
}
uses
}
}
impl Module {
fn taken(&self, binding: &python::Binding, is_submodule: bool, code: &[String]) -> Vec<String> {
let mentioned = |name: &&String| code.iter().any(|line| python_names::mentions(line, name));
match (&binding.bound, &binding.name) {
(None, _) => self.names.iter().filter(mentioned).cloned().collect(),
(Some(_), Some(name)) if !is_submodule => vec![name.clone()],
(Some(bound), _) => {
let read = python_names::taken(code, bound);
let whole = read.whole.then_some(&self.names).into_iter().flatten();
read.names.iter().chain(whole).cloned().collect()
}
}
}
}