@lamentis/naome 1.3.12 → 1.3.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/Cargo.lock +2 -2
  2. package/crates/naome-cli/Cargo.toml +1 -1
  3. package/crates/naome-cli/src/architecture_commands.rs +1 -1
  4. package/crates/naome-core/Cargo.toml +1 -1
  5. package/crates/naome-core/src/architecture/output.rs +26 -4
  6. package/crates/naome-core/src/architecture/rules/external.rs +59 -0
  7. package/crates/naome-core/src/architecture/rules.rs +2 -0
  8. package/crates/naome-core/src/architecture/scan/cache.rs +145 -0
  9. package/crates/naome-core/src/architecture/scan/graph_builder.rs +159 -38
  10. package/crates/naome-core/src/architecture/scan/imports/extractors/swift.rs +48 -0
  11. package/crates/naome-core/src/architecture/scan/imports/extractors.rs +3 -0
  12. package/crates/naome-core/src/architecture/scan/imports/resolver.rs +41 -1
  13. package/crates/naome-core/src/architecture/scan/imports.rs +1 -0
  14. package/crates/naome-core/src/architecture/scan/manifest/common.rs +102 -0
  15. package/crates/naome-core/src/architecture/scan/manifest/parsers/json.rs +46 -0
  16. package/crates/naome-core/src/architecture/scan/manifest/parsers/other.rs +280 -0
  17. package/crates/naome-core/src/architecture/scan/manifest/parsers/toml.rs +184 -0
  18. package/crates/naome-core/src/architecture/scan/manifest/parsers.rs +3 -0
  19. package/crates/naome-core/src/architecture/scan/manifest.rs +33 -0
  20. package/crates/naome-core/src/architecture/scan.rs +254 -10
  21. package/crates/naome-core/tests/architecture_cache.rs +212 -0
  22. package/crates/naome-core/tests/architecture_manifests.rs +289 -0
  23. package/crates/naome-core/tests/architecture_support/mod.rs +2 -0
  24. package/crates/naome-core/tests/architecture_swift.rs +111 -0
  25. package/native/darwin-arm64/naome +0 -0
  26. package/native/linux-x64/naome +0 -0
  27. package/package.json +1 -1
  28. package/templates/naome-root/.naome/manifest.json +2 -2
  29. package/templates/naome-root/docs/naome/architecture-fitness.md +44 -13
@@ -35,6 +35,11 @@ pub(super) fn resolve_import(
35
35
  return ImportTarget::File(path);
36
36
  }
37
37
  }
38
+ if from_path.ends_with(".swift") {
39
+ if let Some(path) = resolve_swift_module_import(from_path, specifier, repository_files) {
40
+ return ImportTarget::File(path);
41
+ }
42
+ }
38
43
  external_target(specifier)
39
44
  }
40
45
 
@@ -225,6 +230,40 @@ fn go_mod_module(root: &Path, go_mod_path: &str) -> Option<String> {
225
230
  })
226
231
  }
227
232
 
233
+ fn resolve_swift_module_import(
234
+ from_path: &str,
235
+ specifier: &str,
236
+ repository_files: &BTreeSet<String>,
237
+ ) -> Option<String> {
238
+ if specifier.contains('/') || specifier.contains('.') {
239
+ return None;
240
+ }
241
+ let sources_root = swift_sources_root(from_path)?;
242
+ let target_prefix = format!("{sources_root}{specifier}/");
243
+ let conventional_file = format!("{target_prefix}{specifier}.swift");
244
+ if repository_files.contains(&conventional_file) {
245
+ return Some(conventional_file);
246
+ }
247
+ repository_files
248
+ .iter()
249
+ .find(|path| path.starts_with(&target_prefix) && path.ends_with(".swift"))
250
+ .cloned()
251
+ }
252
+
253
+ fn swift_sources_root(from_path: &str) -> Option<String> {
254
+ if from_path.starts_with("Sources/") {
255
+ return Some("Sources/".to_string());
256
+ }
257
+ if from_path.starts_with("Tests/") {
258
+ return Some("Sources/".to_string());
259
+ }
260
+ if let Some((prefix, _)) = from_path.split_once("/Sources/") {
261
+ return Some(format!("{prefix}/Sources/"));
262
+ }
263
+ let (prefix, _) = from_path.split_once("/Tests/")?;
264
+ Some(format!("{prefix}/Sources/"))
265
+ }
266
+
228
267
  fn resolve_candidates(
229
268
  candidate: &str,
230
269
  repository_files: &BTreeSet<String>,
@@ -286,10 +325,11 @@ fn extensions_for_language(language: Option<&str>) -> &'static [&'static str] {
286
325
  Some("rust") => &["", ".rs"],
287
326
  Some("python") => &["", ".py"],
288
327
  Some("go") => &["", ".go"],
328
+ Some("swift") => &["", ".swift"],
289
329
  Some("typescript") => &["", ".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"],
290
330
  Some("javascript") => &["", ".js", ".jsx", ".mjs", ".cjs", ".ts", ".tsx"],
291
331
  _ => &[
292
- "", ".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".rs", ".py", ".go",
332
+ "", ".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".rs", ".py", ".go", ".swift",
293
333
  ],
294
334
  }
295
335
  }
@@ -70,6 +70,7 @@ pub(super) fn language_for_path(path: &str) -> Option<&'static str> {
70
70
  "rs" => Some("rust"),
71
71
  "py" => Some("python"),
72
72
  "go" => Some("go"),
73
+ "swift" => Some("swift"),
73
74
  _ => None,
74
75
  }
75
76
  }
@@ -0,0 +1,102 @@
1
+ use std::path::Path;
2
+
3
+ use crate::architecture::scan::{ManifestDependency, ManifestFact};
4
+
5
+ pub(super) enum ManifestKind {
6
+ PackageJson,
7
+ CargoToml,
8
+ PyprojectToml,
9
+ GoMod,
10
+ PomXml,
11
+ BuildGradle,
12
+ PackageSwift,
13
+ XcodeProject,
14
+ }
15
+
16
+ pub(super) fn manifest_kind(path: &str) -> Option<ManifestKind> {
17
+ match path.rsplit('/').next()? {
18
+ "package.json" => Some(ManifestKind::PackageJson),
19
+ "Cargo.toml" => Some(ManifestKind::CargoToml),
20
+ "pyproject.toml" => Some(ManifestKind::PyprojectToml),
21
+ "go.mod" => Some(ManifestKind::GoMod),
22
+ "pom.xml" => Some(ManifestKind::PomXml),
23
+ "build.gradle" | "build.gradle.kts" => Some(ManifestKind::BuildGradle),
24
+ "Package.swift" => Some(ManifestKind::PackageSwift),
25
+ "project.pbxproj" => {
26
+ if path.contains(".xcodeproj/") {
27
+ Some(ManifestKind::XcodeProject)
28
+ } else {
29
+ None
30
+ }
31
+ }
32
+ _ => None,
33
+ }
34
+ }
35
+
36
+ pub(super) fn fact(
37
+ path: &str,
38
+ ecosystem: &str,
39
+ package_name: String,
40
+ mut dependencies: Vec<ManifestDependency>,
41
+ confidence: f32,
42
+ ) -> ManifestFact {
43
+ dependencies.sort_by(|left, right| {
44
+ left.name
45
+ .cmp(&right.name)
46
+ .then(left.dependency_kind.cmp(&right.dependency_kind))
47
+ });
48
+ dependencies.dedup_by(|left, right| {
49
+ left.name == right.name && left.dependency_kind == right.dependency_kind
50
+ });
51
+ ManifestFact {
52
+ path: path.to_string(),
53
+ ecosystem: ecosystem.to_string(),
54
+ package_name,
55
+ dependencies,
56
+ confidence,
57
+ extractor: format!("manifest:{}", manifest_kind_label(path)),
58
+ }
59
+ }
60
+
61
+ pub(super) fn dependency(
62
+ name: impl Into<String>,
63
+ dependency_kind: impl Into<String>,
64
+ version: Option<String>,
65
+ confidence: f32,
66
+ ) -> ManifestDependency {
67
+ ManifestDependency {
68
+ name: name.into(),
69
+ dependency_kind: dependency_kind.into(),
70
+ version,
71
+ confidence,
72
+ }
73
+ }
74
+
75
+ pub(super) fn fallback_package_name(path: &str, ecosystem: &str) -> String {
76
+ let parent = Path::new(path)
77
+ .parent()
78
+ .map(|path| path.to_string_lossy().replace('\\', "/"))
79
+ .filter(|path| !path.is_empty())
80
+ .unwrap_or_else(|| path.to_string());
81
+ format!("{ecosystem}:{parent}")
82
+ }
83
+
84
+ pub(super) fn quoted_value(value: &str) -> Option<String> {
85
+ let trimmed = value.trim();
86
+ trimmed
87
+ .strip_prefix('"')
88
+ .and_then(|value| value.split_once('"').map(|(value, _)| value.to_string()))
89
+ .or_else(|| {
90
+ trimmed
91
+ .strip_prefix('\'')
92
+ .and_then(|value| value.split_once('\'').map(|(value, _)| value.to_string()))
93
+ })
94
+ }
95
+
96
+ pub(super) fn clean_toml_line(line: &str) -> &str {
97
+ line.split('#').next().unwrap_or_default().trim()
98
+ }
99
+
100
+ fn manifest_kind_label(path: &str) -> &str {
101
+ path.rsplit('/').next().unwrap_or(path)
102
+ }
@@ -0,0 +1,46 @@
1
+ use serde_json::Value;
2
+
3
+ use crate::architecture::scan::{ManifestDependency, ManifestFact};
4
+
5
+ use super::super::common::{dependency, fact, fallback_package_name};
6
+
7
+ pub(in crate::architecture::scan::manifest) fn package_json(
8
+ path: &str,
9
+ content: &str,
10
+ ) -> Option<ManifestFact> {
11
+ let value = serde_json::from_str::<Value>(content).ok()?;
12
+ let package_name = value
13
+ .get("name")
14
+ .and_then(Value::as_str)
15
+ .map(str::to_string)
16
+ .unwrap_or_else(|| fallback_package_name(path, "npm"));
17
+ let mut dependencies = Vec::new();
18
+ push_dependency_object(&mut dependencies, &value, "dependencies", "runtime");
19
+ push_dependency_object(&mut dependencies, &value, "devDependencies", "development");
20
+ push_dependency_object(&mut dependencies, &value, "peerDependencies", "peer");
21
+ push_dependency_object(
22
+ &mut dependencies,
23
+ &value,
24
+ "optionalDependencies",
25
+ "optional",
26
+ );
27
+ Some(fact(path, "npm", package_name, dependencies, 0.98))
28
+ }
29
+
30
+ fn push_dependency_object(
31
+ dependencies: &mut Vec<ManifestDependency>,
32
+ value: &Value,
33
+ field: &str,
34
+ kind: &str,
35
+ ) {
36
+ if let Some(object) = value.get(field).and_then(Value::as_object) {
37
+ dependencies.extend(object.iter().map(|(name, version)| {
38
+ dependency(
39
+ name.clone(),
40
+ kind,
41
+ version.as_str().map(str::to_string),
42
+ 0.98,
43
+ )
44
+ }));
45
+ }
46
+ }
@@ -0,0 +1,280 @@
1
+ use crate::architecture::scan::{ManifestDependency, ManifestFact};
2
+
3
+ use super::super::common::{dependency, fact, fallback_package_name, quoted_value};
4
+
5
+ pub(in crate::architecture::scan::manifest) fn go_mod(
6
+ path: &str,
7
+ content: &str,
8
+ ) -> Option<ManifestFact> {
9
+ let package_name = content
10
+ .lines()
11
+ .find_map(|line| line.trim().strip_prefix("module "))
12
+ .map(str::trim)
13
+ .filter(|value| !value.is_empty())
14
+ .map(str::to_string)
15
+ .unwrap_or_else(|| fallback_package_name(path, "go"));
16
+ let mut dependencies = Vec::new();
17
+ let mut in_require_block = false;
18
+ for raw_line in content.lines() {
19
+ let line = raw_line.split("//").next().unwrap_or_default().trim();
20
+ if line == "require (" {
21
+ in_require_block = true;
22
+ continue;
23
+ }
24
+ if in_require_block && line == ")" {
25
+ in_require_block = false;
26
+ continue;
27
+ }
28
+ let require_line = line.strip_prefix("require ").unwrap_or(line);
29
+ if !in_require_block && !line.starts_with("require ") {
30
+ continue;
31
+ }
32
+ if let Some((name, version)) = first_two_fields(require_line) {
33
+ dependencies.push(dependency(name, "runtime", version, 0.95));
34
+ }
35
+ }
36
+ Some(fact(path, "go", package_name, dependencies, 0.95))
37
+ }
38
+
39
+ pub(in crate::architecture::scan::manifest) fn pom_xml(
40
+ path: &str,
41
+ content: &str,
42
+ ) -> Option<ManifestFact> {
43
+ let project_content = xml_without_parent_blocks(content);
44
+ let package_group = first_xml_tag(&project_content, "groupId")
45
+ .or_else(|| first_xml_tag(content, "groupId"))
46
+ .unwrap_or_else(|| "maven".to_string());
47
+ let package_artifact = first_xml_tag(&project_content, "artifactId")
48
+ .unwrap_or_else(|| fallback_package_name(path, "maven"));
49
+ let package_name = format!("{package_group}:{package_artifact}");
50
+ let dependency_content = xml_without_blocks(
51
+ &xml_without_blocks(content, "dependencyManagement"),
52
+ "build",
53
+ );
54
+ let dependencies = project_dependency_sections(&dependency_content)
55
+ .into_iter()
56
+ .flat_map(|section| section.split("<dependency>").skip(1).collect::<Vec<_>>())
57
+ .filter_map(|block| {
58
+ let group = first_xml_tag(block, "groupId")?;
59
+ let artifact = first_xml_tag(block, "artifactId")?;
60
+ Some(dependency(
61
+ format!("{group}:{artifact}"),
62
+ first_xml_tag(block, "scope").unwrap_or_else(|| "runtime".into()),
63
+ first_xml_tag(block, "version"),
64
+ 0.75,
65
+ ))
66
+ })
67
+ .collect();
68
+ Some(fact(path, "maven", package_name, dependencies, 0.75))
69
+ }
70
+
71
+ pub(in crate::architecture::scan::manifest) fn build_gradle(
72
+ path: &str,
73
+ content: &str,
74
+ ) -> Option<ManifestFact> {
75
+ let package_name = fallback_package_name(path, "gradle");
76
+ let dependencies = content.lines().filter_map(gradle_dependency).collect();
77
+ Some(fact(path, "gradle", package_name, dependencies, 0.7))
78
+ }
79
+
80
+ pub(in crate::architecture::scan::manifest) fn package_swift(
81
+ path: &str,
82
+ content: &str,
83
+ ) -> Option<ManifestFact> {
84
+ let active_content = swift_active_content(content);
85
+ let package_name = swift_argument_value(&active_content, "name")
86
+ .unwrap_or_else(|| fallback_package_name(path, "swift"));
87
+ let dependencies = active_content
88
+ .split(".package")
89
+ .skip(1)
90
+ .filter_map(|block| {
91
+ let url = swift_argument_value(block, "url")?;
92
+ Some(dependency(
93
+ package_name_from_url(&url),
94
+ "runtime",
95
+ swift_package_version(block),
96
+ 0.8,
97
+ ))
98
+ })
99
+ .collect();
100
+ Some(fact(path, "swift", package_name, dependencies, 0.8))
101
+ }
102
+
103
+ pub(in crate::architecture::scan::manifest) fn xcode_project(
104
+ path: &str,
105
+ content: &str,
106
+ ) -> Option<ManifestFact> {
107
+ let package_name = fallback_package_name(path, "xcode");
108
+ let dependencies = content
109
+ .lines()
110
+ .filter_map(|line| {
111
+ let url = line
112
+ .trim()
113
+ .strip_prefix("repositoryURL = ")
114
+ .and_then(quoted_value)?;
115
+ Some(dependency(
116
+ package_name_from_url(&url),
117
+ "runtime",
118
+ None,
119
+ 0.65,
120
+ ))
121
+ })
122
+ .collect();
123
+ Some(fact(path, "xcode", package_name, dependencies, 0.65))
124
+ }
125
+
126
+ fn first_two_fields(line: &str) -> Option<(String, Option<String>)> {
127
+ let mut parts = line.split_whitespace();
128
+ Some((parts.next()?.to_string(), parts.next().map(str::to_string)))
129
+ }
130
+
131
+ fn first_xml_tag(content: &str, tag: &str) -> Option<String> {
132
+ let open = format!("<{tag}>");
133
+ let close = format!("</{tag}>");
134
+ content
135
+ .split_once(&open)?
136
+ .1
137
+ .split_once(&close)
138
+ .map(|(value, _)| value.trim().to_string())
139
+ .filter(|value| !value.is_empty())
140
+ }
141
+
142
+ fn xml_without_parent_blocks(content: &str) -> String {
143
+ xml_without_blocks(content, "parent")
144
+ }
145
+
146
+ fn xml_without_blocks(content: &str, tag: &str) -> String {
147
+ let open = format!("<{tag}>");
148
+ let close = format!("</{tag}>");
149
+ let mut remaining = content;
150
+ let mut output = String::new();
151
+ while let Some((before, after_open)) = remaining.split_once(&open) {
152
+ output.push_str(before);
153
+ let Some((_, after_close)) = after_open.split_once(&close) else {
154
+ output.push_str(after_open);
155
+ return output;
156
+ };
157
+ remaining = after_close;
158
+ }
159
+ output.push_str(remaining);
160
+ output
161
+ }
162
+
163
+ fn project_dependency_sections(content: &str) -> Vec<&str> {
164
+ content
165
+ .split("<dependencies>")
166
+ .skip(1)
167
+ .filter_map(|block| block.split_once("</dependencies>").map(|(block, _)| block))
168
+ .collect()
169
+ }
170
+
171
+ fn gradle_dependency(line: &str) -> Option<ManifestDependency> {
172
+ let trimmed = line.split("//").next().unwrap_or_default().trim();
173
+ if trimmed.is_empty() {
174
+ return None;
175
+ }
176
+ let kind = [
177
+ "implementation",
178
+ "api",
179
+ "compileOnly",
180
+ "runtimeOnly",
181
+ "testImplementation",
182
+ ]
183
+ .into_iter()
184
+ .find(|kind| trimmed.contains(&format!("{kind} ")) || trimmed.contains(&format!("{kind}(")))?;
185
+ let coordinate_text = trimmed
186
+ .split_once(&format!("{kind} "))
187
+ .map(|(_, value)| value.trim())
188
+ .or_else(|| {
189
+ trimmed
190
+ .split_once(&format!("{kind}("))
191
+ .map(|(_, value)| value.trim_end_matches(')').trim())
192
+ })?;
193
+ if let Some(coordinate) = quoted_value(coordinate_text) {
194
+ return gradle_coordinate_dependency(&coordinate, kind);
195
+ }
196
+ let group = gradle_map_value(coordinate_text, "group")?;
197
+ let artifact = gradle_map_value(coordinate_text, "name")?;
198
+ Some(dependency(
199
+ format!("{group}:{artifact}"),
200
+ kind,
201
+ gradle_map_value(coordinate_text, "version"),
202
+ 0.7,
203
+ ))
204
+ }
205
+
206
+ fn gradle_coordinate_dependency(coordinate: &str, kind: &str) -> Option<ManifestDependency> {
207
+ let mut parts = coordinate.split(':');
208
+ let group = parts.next()?;
209
+ let artifact = parts.next()?;
210
+ Some(dependency(
211
+ format!("{group}:{artifact}"),
212
+ kind,
213
+ parts.next().map(str::to_string),
214
+ 0.7,
215
+ ))
216
+ }
217
+
218
+ fn gradle_map_value(text: &str, key: &str) -> Option<String> {
219
+ text.split(',').find_map(|part| {
220
+ let (name, value) = part.trim().split_once(':')?;
221
+ if name.trim() == key {
222
+ quoted_value(value.trim())
223
+ } else {
224
+ None
225
+ }
226
+ })
227
+ }
228
+
229
+ fn swift_argument_value(content: &str, key: &str) -> Option<String> {
230
+ let marker = format!("{key}:");
231
+ let (_, value) = content.split_once(&marker)?;
232
+ quoted_value(value.trim())
233
+ }
234
+
235
+ fn swift_active_content(content: &str) -> String {
236
+ content
237
+ .lines()
238
+ .map(strip_swift_line_comment)
239
+ .collect::<Vec<_>>()
240
+ .join("\n")
241
+ }
242
+
243
+ fn strip_swift_line_comment(line: &str) -> &str {
244
+ let mut in_quote = false;
245
+ let mut escaped = false;
246
+ let mut previous = '\0';
247
+ for (index, character) in line.char_indices() {
248
+ if in_quote {
249
+ if character == '"' && !escaped {
250
+ in_quote = false;
251
+ }
252
+ escaped = character == '\\' && !escaped;
253
+ } else if character == '"' {
254
+ in_quote = true;
255
+ escaped = false;
256
+ } else if previous == '/' && character == '/' {
257
+ return &line[..index - 1];
258
+ }
259
+ if character != '\\' {
260
+ escaped = false;
261
+ }
262
+ previous = character;
263
+ }
264
+ line
265
+ }
266
+
267
+ fn swift_package_version(block: &str) -> Option<String> {
268
+ swift_argument_value(block, "from")
269
+ .or_else(|| swift_argument_value(block, "exact"))
270
+ .or_else(|| swift_argument_value(block, "branch"))
271
+ }
272
+
273
+ fn package_name_from_url(url: &str) -> String {
274
+ url.trim_end_matches(".git")
275
+ .trim_end_matches('/')
276
+ .rsplit('/')
277
+ .next()
278
+ .unwrap_or(url)
279
+ .to_string()
280
+ }
@@ -0,0 +1,184 @@
1
+ use crate::architecture::scan::{ManifestDependency, ManifestFact};
2
+
3
+ use super::super::common::{
4
+ clean_toml_line, dependency, fact, fallback_package_name, quoted_value,
5
+ };
6
+
7
+ pub(in crate::architecture::scan::manifest) fn cargo_toml(
8
+ path: &str,
9
+ content: &str,
10
+ ) -> Option<ManifestFact> {
11
+ let package_name = toml_string(content, "package", "name")
12
+ .unwrap_or_else(|| fallback_package_name(path, "cargo"));
13
+ let dependencies = toml_dependency_sections(
14
+ content,
15
+ &[
16
+ ("dependencies", "runtime"),
17
+ ("dev-dependencies", "development"),
18
+ ("build-dependencies", "build"),
19
+ ],
20
+ );
21
+ Some(fact(path, "cargo", package_name, dependencies, 0.9))
22
+ }
23
+
24
+ pub(in crate::architecture::scan::manifest) fn pyproject_toml(
25
+ path: &str,
26
+ content: &str,
27
+ ) -> Option<ManifestFact> {
28
+ let package_name = toml_string(content, "project", "name")
29
+ .unwrap_or_else(|| fallback_package_name(path, "python"));
30
+ let mut dependencies = toml_string_array(content, "project", "dependencies")
31
+ .into_iter()
32
+ .map(|value| dependency(python_dependency_name(&value), "runtime", Some(value), 0.85))
33
+ .collect::<Vec<_>>();
34
+ dependencies.extend(
35
+ toml_dependency_sections(content, &[("tool.poetry.dependencies", "runtime")])
36
+ .into_iter()
37
+ .filter(|dependency| dependency.name != "python"),
38
+ );
39
+ Some(fact(path, "python", package_name, dependencies, 0.85))
40
+ }
41
+
42
+ fn toml_string(content: &str, section: &str, key: &str) -> Option<String> {
43
+ section_value(content, section, key).and_then(quoted_value)
44
+ }
45
+
46
+ fn toml_string_array(content: &str, section: &str, key: &str) -> Vec<String> {
47
+ let mut current_section = "";
48
+ let mut collecting = false;
49
+ let mut array = String::new();
50
+ for line in content.lines().map(clean_toml_line) {
51
+ if !collecting {
52
+ if let Some(section_name) = line
53
+ .strip_prefix('[')
54
+ .and_then(|line| line.strip_suffix(']'))
55
+ {
56
+ current_section = section_name.trim();
57
+ continue;
58
+ }
59
+ if current_section != section {
60
+ continue;
61
+ }
62
+ let Some(value) = line
63
+ .strip_prefix(key)
64
+ .and_then(|line| line.trim().strip_prefix('='))
65
+ else {
66
+ continue;
67
+ };
68
+ array.push_str(value.trim());
69
+ if value.contains(']') {
70
+ break;
71
+ }
72
+ collecting = value.trim_start().starts_with('[');
73
+ continue;
74
+ }
75
+ array.push(' ');
76
+ array.push_str(line);
77
+ if line.contains(']') {
78
+ break;
79
+ }
80
+ }
81
+ array
82
+ .trim()
83
+ .trim_start_matches('[')
84
+ .trim_end_matches(']')
85
+ .split(',')
86
+ .filter_map(quoted_value)
87
+ .collect()
88
+ }
89
+
90
+ fn section_value<'a>(content: &'a str, section: &str, key: &str) -> Option<&'a str> {
91
+ let mut current_section = "";
92
+ for line in content.lines().map(clean_toml_line) {
93
+ if let Some(section_name) = line
94
+ .strip_prefix('[')
95
+ .and_then(|line| line.strip_suffix(']'))
96
+ {
97
+ current_section = section_name.trim();
98
+ continue;
99
+ }
100
+ if current_section == section {
101
+ if let Some(value) = line
102
+ .strip_prefix(key)
103
+ .and_then(|line| line.trim().strip_prefix('='))
104
+ {
105
+ return Some(value.trim());
106
+ }
107
+ }
108
+ }
109
+ None
110
+ }
111
+
112
+ fn toml_dependency_sections(content: &str, sections: &[(&str, &str)]) -> Vec<ManifestDependency> {
113
+ let mut current_kind = None;
114
+ let mut table_dependency = None;
115
+ let mut dependencies = Vec::new();
116
+ for line in content.lines().map(clean_toml_line) {
117
+ if let Some(section_name) = line
118
+ .strip_prefix('[')
119
+ .and_then(|line| line.strip_suffix(']'))
120
+ {
121
+ push_table_dependency(&mut dependencies, table_dependency.take());
122
+ let section_name = section_name.trim();
123
+ current_kind = sections
124
+ .iter()
125
+ .find(|(section, _)| *section == section_name)
126
+ .map(|(_, kind)| *kind);
127
+ if current_kind.is_none() {
128
+ table_dependency = dependency_table(section_name, sections);
129
+ }
130
+ continue;
131
+ }
132
+ if let Some((name, kind, version)) = table_dependency.as_mut() {
133
+ if let Some((key, value)) = line.split_once('=') {
134
+ if key.trim() == "version" {
135
+ *version = quoted_value(value.trim());
136
+ }
137
+ }
138
+ if !name.is_empty() && !kind.is_empty() {
139
+ continue;
140
+ }
141
+ }
142
+ if let (Some(kind), Some((name, value))) = (current_kind, line.split_once('=')) {
143
+ dependencies.push(dependency(
144
+ name.trim().to_string(),
145
+ kind,
146
+ quoted_value(value.trim()),
147
+ 0.85,
148
+ ));
149
+ }
150
+ }
151
+ push_table_dependency(&mut dependencies, table_dependency);
152
+ dependencies
153
+ }
154
+
155
+ fn dependency_table<'a>(
156
+ section_name: &str,
157
+ sections: &[(&str, &'a str)],
158
+ ) -> Option<(String, &'a str, Option<String>)> {
159
+ sections.iter().find_map(|(section, kind)| {
160
+ let dependency_name = section_name.strip_prefix(&format!("{section}."))?;
161
+ if dependency_name.is_empty() || dependency_name.contains('.') {
162
+ return None;
163
+ }
164
+ Some((dependency_name.to_string(), *kind, None))
165
+ })
166
+ }
167
+
168
+ fn push_table_dependency(
169
+ dependencies: &mut Vec<ManifestDependency>,
170
+ table_dependency: Option<(String, &str, Option<String>)>,
171
+ ) {
172
+ if let Some((name, kind, version)) = table_dependency {
173
+ dependencies.push(dependency(name, kind, version, 0.85));
174
+ }
175
+ }
176
+
177
+ fn python_dependency_name(value: &str) -> String {
178
+ value
179
+ .split(['<', '>', '=', '!', '~', '[', ';', ' '])
180
+ .next()
181
+ .unwrap_or(value)
182
+ .trim()
183
+ .to_string()
184
+ }
@@ -0,0 +1,3 @@
1
+ pub(super) mod json;
2
+ pub(super) mod other;
3
+ pub(super) mod toml;