use std::collections::HashMap;
use std::path::{Path, PathBuf};
use crate::discovery::node_modules;
use crate::discovery::package_json::{self, PackageJson, get_module_iri};
use crate::error::Result;
use crate::fs::Fs;
#[derive(Debug, Clone)]
pub struct ModuleState {
pub main_module_path: PathBuf,
pub node_module_import_paths: Vec<PathBuf>,
pub node_module_paths: Vec<PathBuf>,
pub package_jsons: HashMap<PathBuf, PackageJson>,
pub component_modules: HashMap<String, HashMap<u64, PathBuf>>,
pub contexts: HashMap<String, serde_json::Value>,
pub import_paths: HashMap<String, PathBuf>,
}
impl ModuleState {
pub async fn build(fs: &dyn Fs, main_module_path: &Path) -> Result<Self> {
let main_module_path = fs
.canonicalize(main_module_path)
.await?;
tracing::info!("Building module state from: {}", main_module_path.display());
let node_module_import_paths =
node_modules::build_node_module_import_paths(&main_module_path);
let node_module_paths =
node_modules::build_node_module_paths(fs, &node_module_import_paths).await?;
tracing::info!("Discovered {} node module paths", node_module_paths.len());
let mut package_jsons = package_json::read_package_jsons(fs, &node_module_paths).await?;
package_json::preprocess_all(fs, &mut package_jsons).await;
let component_modules = build_component_modules(&package_jsons)?;
let contexts = build_component_contexts(fs, &package_jsons).await?;
let import_paths = build_component_import_paths(&package_jsons)?;
tracing::info!(
"Found {} component modules, {} contexts, {} import paths",
component_modules.len(),
contexts.len(),
import_paths.len()
);
Ok(ModuleState {
main_module_path,
node_module_import_paths,
node_module_paths,
package_jsons,
component_modules,
contexts,
import_paths,
})
}
}
fn build_component_modules(
package_jsons: &HashMap<PathBuf, PackageJson>,
) -> Result<HashMap<String, HashMap<u64, PathBuf>>> {
let mut modules: HashMap<String, HashMap<u64, PathBuf>> = HashMap::new();
let mut versions: HashMap<String, HashMap<u64, semver::Version>> = HashMap::new();
for (module_path, pkg) in package_jsons {
let Some(module_iri) = get_module_iri(pkg) else {
continue;
};
let Some(components_rel) = &pkg.lsd_components else {
continue;
};
let Ok(version) = semver::Version::parse(&pkg.version) else {
continue;
};
let major = version.major;
let absolute_path = module_path.join(components_rel);
let entry = modules.entry(module_iri.clone()).or_default();
let ver_entry = versions.entry(module_iri.clone()).or_default();
if let Some(existing_ver) = ver_entry.get(&major) {
if &version > existing_ver {
entry.insert(major, absolute_path);
ver_entry.insert(major, version);
}
} else {
entry.insert(major, absolute_path);
ver_entry.insert(major, version);
}
}
Ok(modules)
}
async fn build_component_contexts(
fs: &dyn Fs,
package_jsons: &HashMap<PathBuf, PackageJson>,
) -> Result<HashMap<String, serde_json::Value>> {
let mut contexts: HashMap<String, serde_json::Value> = HashMap::new();
let mut ctx_versions: HashMap<String, semver::Version> = HashMap::new();
for (module_path, pkg) in package_jsons {
let Some(ctx_map) = &pkg.lsd_contexts else {
continue;
};
let Ok(version) = semver::Version::parse(&pkg.version) else {
continue;
};
for (ctx_iri, rel_path) in ctx_map {
let file_path = module_path.join(rel_path);
if let Some(existing_ver) = ctx_versions.get(ctx_iri) {
if &version <= existing_ver {
continue;
}
}
match fs.read_to_string(&file_path).await {
Ok(contents) => match serde_json::from_str(&contents) {
Ok(parsed) => {
contexts.insert(ctx_iri.clone(), parsed);
ctx_versions.insert(ctx_iri.clone(), version.clone());
}
Err(e) => {
tracing::warn!(
"Failed to parse context file {}: {}",
file_path.display(),
e
);
}
},
Err(e) => {
tracing::warn!(
"Failed to read context file {}: {}",
file_path.display(),
e
);
}
}
}
}
Ok(contexts)
}
fn build_component_import_paths(
package_jsons: &HashMap<PathBuf, PackageJson>,
) -> Result<HashMap<String, PathBuf>> {
let mut import_paths: HashMap<String, PathBuf> = HashMap::new();
let mut path_versions: HashMap<String, semver::Version> = HashMap::new();
for (module_path, pkg) in package_jsons {
let Some(ip_map) = &pkg.lsd_import_paths else {
continue;
};
let Ok(version) = semver::Version::parse(&pkg.version) else {
continue;
};
for (iri_prefix, rel_path) in ip_map {
let abs_path = module_path.join(rel_path);
if let Some(existing_ver) = path_versions.get(iri_prefix) {
if &version <= existing_ver {
continue;
}
}
import_paths.insert(iri_prefix.clone(), abs_path);
path_versions.insert(iri_prefix.clone(), version.clone());
}
}
Ok(import_paths)
}