components_rs/
module_state.rs1use std::collections::HashMap;
2use std::path::{Path, PathBuf};
3
4use crate::discovery::node_modules;
5use crate::discovery::package_json::{self, PackageJson, get_module_iri};
6use crate::error::Result;
7use crate::fs::Fs;
8
9#[derive(Debug, Clone)]
12pub struct ModuleState {
13 pub main_module_path: PathBuf,
15 pub node_module_import_paths: Vec<PathBuf>,
17 pub node_module_paths: Vec<PathBuf>,
19 pub package_jsons: HashMap<PathBuf, PackageJson>,
21 pub component_modules: HashMap<String, HashMap<u64, PathBuf>>,
23 pub contexts: HashMap<String, serde_json::Value>,
25 pub import_paths: HashMap<String, PathBuf>,
27}
28
29impl ModuleState {
30 pub async fn build(fs: &dyn Fs, main_module_path: &Path) -> Result<Self> {
32 let main_module_path = fs
33 .canonicalize(main_module_path)
34 .await?;
35
36 tracing::info!("Building module state from: {}", main_module_path.display());
37
38 let node_module_import_paths =
39 node_modules::build_node_module_import_paths(&main_module_path);
40 let node_module_paths =
41 node_modules::build_node_module_paths(fs, &node_module_import_paths).await?;
42
43 tracing::info!("Discovered {} node module paths", node_module_paths.len());
44
45 let mut package_jsons = package_json::read_package_jsons(fs, &node_module_paths).await?;
46 package_json::preprocess_all(fs, &mut package_jsons).await;
47
48 let component_modules = build_component_modules(&package_jsons)?;
49 let contexts = build_component_contexts(fs, &package_jsons).await?;
50 let import_paths = build_component_import_paths(&package_jsons)?;
51
52 tracing::info!(
53 "Found {} component modules, {} contexts, {} import paths",
54 component_modules.len(),
55 contexts.len(),
56 import_paths.len()
57 );
58
59 Ok(ModuleState {
60 main_module_path,
61 node_module_import_paths,
62 node_module_paths,
63 package_jsons,
64 component_modules,
65 contexts,
66 import_paths,
67 })
68 }
69}
70
71fn build_component_modules(
73 package_jsons: &HashMap<PathBuf, PackageJson>,
74) -> Result<HashMap<String, HashMap<u64, PathBuf>>> {
75 let mut modules: HashMap<String, HashMap<u64, PathBuf>> = HashMap::new();
76 let mut versions: HashMap<String, HashMap<u64, semver::Version>> = HashMap::new();
77
78 for (module_path, pkg) in package_jsons {
79 let Some(module_iri) = get_module_iri(pkg) else {
80 continue;
81 };
82 let Some(components_rel) = &pkg.lsd_components else {
83 continue;
84 };
85 let Ok(version) = semver::Version::parse(&pkg.version) else {
86 continue;
87 };
88
89 let major = version.major;
90 let absolute_path = module_path.join(components_rel);
91
92 let entry = modules.entry(module_iri.clone()).or_default();
93 let ver_entry = versions.entry(module_iri.clone()).or_default();
94
95 if let Some(existing_ver) = ver_entry.get(&major) {
96 if &version > existing_ver {
97 entry.insert(major, absolute_path);
98 ver_entry.insert(major, version);
99 }
100 } else {
101 entry.insert(major, absolute_path);
102 ver_entry.insert(major, version);
103 }
104 }
105
106 Ok(modules)
107}
108
109async fn build_component_contexts(
111 fs: &dyn Fs,
112 package_jsons: &HashMap<PathBuf, PackageJson>,
113) -> Result<HashMap<String, serde_json::Value>> {
114 let mut contexts: HashMap<String, serde_json::Value> = HashMap::new();
115 let mut ctx_versions: HashMap<String, semver::Version> = HashMap::new();
116
117 for (module_path, pkg) in package_jsons {
118 let Some(ctx_map) = &pkg.lsd_contexts else {
119 continue;
120 };
121 let Ok(version) = semver::Version::parse(&pkg.version) else {
122 continue;
123 };
124
125 for (ctx_iri, rel_path) in ctx_map {
126 let file_path = module_path.join(rel_path);
127
128 if let Some(existing_ver) = ctx_versions.get(ctx_iri) {
130 if &version <= existing_ver {
131 continue;
132 }
133 }
134
135 match fs.read_to_string(&file_path).await {
136 Ok(contents) => match serde_json::from_str(&contents) {
137 Ok(parsed) => {
138 contexts.insert(ctx_iri.clone(), parsed);
139 ctx_versions.insert(ctx_iri.clone(), version.clone());
140 }
141 Err(e) => {
142 tracing::warn!(
143 "Failed to parse context file {}: {}",
144 file_path.display(),
145 e
146 );
147 }
148 },
149 Err(e) => {
150 tracing::warn!(
151 "Failed to read context file {}: {}",
152 file_path.display(),
153 e
154 );
155 }
156 }
157 }
158 }
159
160 Ok(contexts)
161}
162
163fn build_component_import_paths(
165 package_jsons: &HashMap<PathBuf, PackageJson>,
166) -> Result<HashMap<String, PathBuf>> {
167 let mut import_paths: HashMap<String, PathBuf> = HashMap::new();
168 let mut path_versions: HashMap<String, semver::Version> = HashMap::new();
169
170 for (module_path, pkg) in package_jsons {
171 let Some(ip_map) = &pkg.lsd_import_paths else {
172 continue;
173 };
174 let Ok(version) = semver::Version::parse(&pkg.version) else {
175 continue;
176 };
177
178 for (iri_prefix, rel_path) in ip_map {
179 let abs_path = module_path.join(rel_path);
180
181 if let Some(existing_ver) = path_versions.get(iri_prefix) {
182 if &version <= existing_ver {
183 continue;
184 }
185 }
186
187 import_paths.insert(iri_prefix.clone(), abs_path);
188 path_versions.insert(iri_prefix.clone(), version.clone());
189 }
190 }
191
192 Ok(import_paths)
193}