use std::collections::HashMap;
use std::sync::Arc;
use rdf_parsers::jsonld::convert::{parse_json, JsonLdVal};
use url::Url;
use crate::discovery::node_modules;
use crate::discovery::package_json::{self, get_module_iri, PackageJson};
use crate::error::Result;
use crate::fs::Fs;
#[derive(Debug, Clone)]
pub struct ModuleState {
pub main_module_path: Url,
pub node_module_import_paths: Vec<Url>,
pub node_module_paths: Vec<Url>,
pub package_jsons: HashMap<Url, PackageJson>,
pub component_modules: HashMap<String, HashMap<u64, Url>>,
pub contexts: Arc<HashMap<String, JsonLdVal>>,
pub import_paths: HashMap<String, Url>,
pub context_urls: HashMap<String, Url>,
}
impl ModuleState {
pub fn empty() -> Self {
Self {
main_module_path: Url::parse("file:///tmp/").unwrap(),
node_module_import_paths: Vec::new(),
node_module_paths: Vec::new(),
package_jsons: HashMap::new(),
component_modules: HashMap::new(),
contexts: Arc::new(HashMap::new()),
import_paths: HashMap::new(),
context_urls: HashMap::new(),
}
}
pub async fn build(fs: &dyn Fs, main_module_path: &Url) -> Result<Self> {
tracing::info!(
"[CJS] Building module state from: {}",
main_module_path.as_str()
);
let node_module_import_paths = vec![main_module_path.clone()];
let node_module_paths =
node_modules::build_node_module_paths(fs, &node_module_import_paths, false).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 = Arc::new(build_component_contexts(fs, &package_jsons).await?);
let import_paths = build_component_import_paths(&package_jsons)?;
let context_urls = build_component_context_urls(&package_jsons)?;
tracing::info!(
"[CJS] Found {} component modules, {} contexts, {} import paths",
component_modules.len(),
contexts.len(),
import_paths.len()
);
Ok(ModuleState {
main_module_path: main_module_path.clone(),
node_module_import_paths,
node_module_paths,
package_jsons,
component_modules,
contexts,
import_paths,
context_urls,
})
}
}
fn build_component_modules(
package_jsons: &HashMap<Url, PackageJson>,
) -> Result<HashMap<String, HashMap<u64, Url>>> {
let mut modules: HashMap<String, HashMap<u64, Url>> = 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 absolute_path = match module_path.join(components_rel) {
Ok(u) => u,
Err(_) => continue,
};
let major = version.major;
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<Url, PackageJson>,
) -> Result<HashMap<String, JsonLdVal>> {
let mut contexts: HashMap<String, JsonLdVal> = 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_url = match module_path.join(rel_path) {
Ok(u) => u,
Err(_) => continue,
};
if let Some(existing_ver) = ctx_versions.get(ctx_iri) {
if &version <= existing_ver {
continue;
}
}
match fs.read_to_string(&file_url).await {
Ok(contents) => match parse_json(&contents) {
Some(parsed) => {
contexts.insert(ctx_iri.clone(), parsed);
ctx_versions.insert(ctx_iri.clone(), version.clone());
}
None => {
tracing::warn!("Failed to parse context file {}", file_url.as_str());
}
},
Err(e) => {
tracing::warn!("Failed to read context file {}: {}", file_url.as_str(), e);
}
}
}
}
Ok(contexts)
}
fn build_component_import_paths(
package_jsons: &HashMap<Url, PackageJson>,
) -> Result<HashMap<String, Url>> {
let mut import_paths: HashMap<String, Url> = 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 rel_dir = if rel_path.ends_with('/') {
rel_path.clone()
} else {
format!("{}/", rel_path)
};
let abs_url = match module_path.join(&rel_dir) {
Ok(u) => u,
Err(e) => {
tracing::warn!("Failed to join import path: {}", e);
continue;
}
};
if let Some(existing_ver) = path_versions.get(iri_prefix) {
if &version <= existing_ver {
continue;
}
}
import_paths.insert(iri_prefix.clone(), abs_url);
path_versions.insert(iri_prefix.clone(), version.clone());
}
}
Ok(import_paths)
}
fn build_component_context_urls(
package_jsons: &HashMap<Url, PackageJson>,
) -> Result<HashMap<String, Url>> {
let mut context_urls: HashMap<String, Url> = 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_url = match module_path.join(rel_path) {
Ok(u) => u,
Err(_) => continue,
};
if let Some(existing_ver) = ctx_versions.get(ctx_iri) {
if &version <= existing_ver {
continue;
}
}
context_urls.insert(ctx_iri.clone(), file_url);
ctx_versions.insert(ctx_iri.clone(), version.clone());
}
}
Ok(context_urls)
}