venus_core/compile/
dependency_parser.rs1use std::collections::hash_map::DefaultHasher;
23use std::hash::{Hash, Hasher};
24use std::path::PathBuf;
25
26#[derive(Debug, Clone, PartialEq, Eq, Hash)]
31pub struct ExternalDependency {
32 pub name: String,
34
35 pub version: Option<String>,
37
38 pub features: Vec<String>,
40
41 pub path: Option<PathBuf>,
43}
44
45impl ExternalDependency {
46 pub fn simple(name: impl Into<String>, version: impl Into<String>) -> Self {
48 Self {
49 name: name.into(),
50 version: Some(version.into()),
51 features: Vec::new(),
52 path: None,
53 }
54 }
55
56 pub fn path_dep(name: impl Into<String>, path: impl Into<PathBuf>) -> Self {
58 Self {
59 name: name.into(),
60 version: None,
61 features: Vec::new(),
62 path: Some(path.into()),
63 }
64 }
65
66 pub fn with_features(mut self, features: Vec<String>) -> Self {
68 self.features = features;
69 self
70 }
71}
72
73pub struct DependencyParser {
75 dependencies: Vec<ExternalDependency>,
76}
77
78impl DependencyParser {
79 pub fn new() -> Self {
81 Self {
82 dependencies: Vec::new(),
83 }
84 }
85
86 pub fn parse(&mut self, source: &str) -> &[ExternalDependency] {
97 self.dependencies.clear();
98
99 let mut in_cargo_block = false;
100 let mut in_dependencies = false;
101 let mut toml_content = String::new();
102
103 for line in source.lines() {
104 let trimmed = line.trim();
105
106 if trimmed.starts_with("//!") {
108 let content = trimmed.trim_start_matches("//!").trim();
109
110 if content == "```cargo" {
111 in_cargo_block = true;
112 continue;
113 }
114
115 if content == "```" && in_cargo_block {
116 in_cargo_block = false;
117 in_dependencies = false;
118 continue;
119 }
120
121 if in_cargo_block {
122 if content == "[dependencies]" {
123 in_dependencies = true;
124 continue;
125 }
126
127 if content.starts_with('[') {
128 in_dependencies = false;
129 continue;
130 }
131
132 if in_dependencies && !content.is_empty() {
133 toml_content.push_str(content);
134 toml_content.push('\n');
135 }
136 }
137 }
138 }
139
140 if !toml_content.is_empty() {
142 self.parse_toml_dependencies(&toml_content);
143 }
144
145 &self.dependencies
146 }
147
148 pub fn dependencies(&self) -> &[ExternalDependency] {
150 &self.dependencies
151 }
152
153 pub fn calculate_hash(&self) -> u64 {
155 let mut hasher = DefaultHasher::new();
156 self.dependencies.hash(&mut hasher);
157 hasher.finish()
158 }
159
160 fn parse_toml_dependencies(&mut self, toml: &str) {
162 for line in toml.lines() {
163 let line = line.trim();
164 if line.is_empty() || line.starts_with('#') {
165 continue;
166 }
167
168 if let Some((name, value)) = line.split_once('=') {
170 let name = name.trim().to_string();
171 let value = value.trim();
172
173 let dep = if value.starts_with('"') {
174 let version = value.trim_matches('"').to_string();
176 ExternalDependency {
177 name,
178 version: Some(version),
179 features: Vec::new(),
180 path: None,
181 }
182 } else if value.starts_with('{') {
183 Self::parse_table_dependency(name, value)
185 } else {
186 continue;
187 };
188
189 self.dependencies.push(dep);
190 }
191 }
192 }
193
194 fn parse_table_dependency(name: String, value: &str) -> ExternalDependency {
196 let mut version = None;
197 let mut features = Vec::new();
198 let mut path = None;
199
200 let content = value.trim_start_matches('{').trim_end_matches('}');
202
203 for part in content.split(',') {
204 let part = part.trim();
205 if let Some((key, val)) = part.split_once('=') {
206 let key = key.trim();
207 let val = val.trim();
208
209 match key {
210 "version" => {
211 version = Some(val.trim_matches('"').to_string());
212 }
213 "path" => {
214 path = Some(PathBuf::from(val.trim_matches('"')));
215 }
216 "features" => {
217 let arr = val.trim_start_matches('[').trim_end_matches(']');
219 for feat in arr.split(',') {
220 let feat = feat.trim().trim_matches('"');
221 if !feat.is_empty() {
222 features.push(feat.to_string());
223 }
224 }
225 }
226 _ => {}
227 }
228 }
229 }
230
231 ExternalDependency {
232 name,
233 version,
234 features,
235 path,
236 }
237 }
238}
239
240impl Default for DependencyParser {
241 fn default() -> Self {
242 Self::new()
243 }
244}
245
246#[cfg(test)]
247mod tests {
248 use super::*;
249
250 #[test]
251 fn test_parse_simple_dependency() {
252 let mut parser = DependencyParser::new();
253
254 let source = r#"
255//! # My Notebook
256//!
257//! ```cargo
258//! [dependencies]
259//! serde = "1.0"
260//! ```
261
262#[venus::cell]
263pub fn hello() -> i32 { 42 }
264"#;
265
266 let deps = parser.parse(source);
267
268 assert_eq!(deps.len(), 1);
269 assert_eq!(deps[0].name, "serde");
270 assert_eq!(deps[0].version, Some("1.0".to_string()));
271 }
272
273 #[test]
274 fn test_parse_complex_dependency() {
275 let mut parser = DependencyParser::new();
276
277 let source = r#"
278//! ```cargo
279//! [dependencies]
280//! tokio = { version = "1", features = ["full"] }
281//! ```
282"#;
283
284 let deps = parser.parse(source);
285
286 assert_eq!(deps.len(), 1);
287 assert_eq!(deps[0].name, "tokio");
288 assert_eq!(deps[0].version, Some("1".to_string()));
289 assert_eq!(deps[0].features, vec!["full"]);
290 }
291
292 #[test]
293 fn test_parse_multiple_dependencies() {
294 let mut parser = DependencyParser::new();
295
296 let source = r#"
297//! ```cargo
298//! [dependencies]
299//! serde = "1.0"
300//! serde_json = "1.0"
301//! tokio = { version = "1", features = ["rt", "macros"] }
302//! ```
303"#;
304
305 let deps = parser.parse(source);
306
307 assert_eq!(deps.len(), 3);
308 }
309
310 #[test]
311 fn test_hash_changes_with_deps() {
312 let mut parser = DependencyParser::new();
313
314 parser.parse("");
315 let hash1 = parser.calculate_hash();
316
317 parser.parse(
318 r#"
319//! ```cargo
320//! [dependencies]
321//! serde = "1.0"
322//! ```
323"#,
324 );
325 let hash2 = parser.calculate_hash();
326
327 assert_ne!(hash1, hash2);
328 }
329
330 #[test]
331 fn test_dependency_builders() {
332 let dep = ExternalDependency::simple("serde", "1.0")
333 .with_features(vec!["derive".to_string()]);
334
335 assert_eq!(dep.name, "serde");
336 assert_eq!(dep.version, Some("1.0".to_string()));
337 assert_eq!(dep.features, vec!["derive"]);
338 }
339}