1use crate::ManifestParser;
4use kdo_core::{DepKind, Dependency, KdoError, Language, Project};
5use std::path::Path;
6use tracing::debug;
7
8pub struct CargoParser;
10
11impl ManifestParser for CargoParser {
12 fn manifest_name(&self) -> &str {
13 "Cargo.toml"
14 }
15
16 fn can_parse(&self, manifest_path: &Path) -> bool {
17 manifest_path
18 .file_name()
19 .map(|f| f == "Cargo.toml")
20 .unwrap_or(false)
21 }
22
23 fn parse(
24 &self,
25 manifest_path: &Path,
26 workspace_root: &Path,
27 ) -> Result<(Project, Vec<Dependency>), KdoError> {
28 let content = std::fs::read_to_string(manifest_path)?;
29 let doc: toml::Value = toml::from_str(&content).map_err(|e| KdoError::ParseError {
30 path: manifest_path.to_path_buf(),
31 source: e.into(),
32 })?;
33
34 if doc.get("workspace").is_some() && doc.get("package").is_none() {
36 return Err(KdoError::ManifestNotFound(manifest_path.to_path_buf()));
37 }
38
39 let package = doc.get("package").ok_or_else(|| KdoError::ParseError {
40 path: manifest_path.to_path_buf(),
41 source: anyhow::anyhow!("missing [package] table"),
42 })?;
43
44 let name = package
45 .get("name")
46 .and_then(|v| v.as_str())
47 .ok_or_else(|| KdoError::ParseError {
48 path: manifest_path.to_path_buf(),
49 source: anyhow::anyhow!("missing package.name"),
50 })?
51 .to_string();
52
53 let description = package
54 .get("description")
55 .and_then(|v| v.as_str())
56 .map(String::from);
57
58 let project_dir = manifest_path
59 .parent()
60 .unwrap_or(Path::new("."))
61 .to_path_buf();
62
63 debug!(name = %name, path = %project_dir.display(), "parsed Cargo.toml");
64
65 let mut deps = Vec::new();
66
67 for (section, kind) in [
69 ("dependencies", DepKind::Source),
70 ("dev-dependencies", DepKind::Dev),
71 ("build-dependencies", DepKind::Build),
72 ] {
73 if let Some(table) = doc.get(section).and_then(|v| v.as_table()) {
74 for (dep_name, dep_val) in table {
75 let dep = parse_cargo_dep(dep_name, dep_val, &kind, workspace_root);
76 deps.push(dep);
77 }
78 }
79 }
80
81 let project = Project {
82 name,
83 path: project_dir,
84 language: Language::Rust,
85 manifest_path: manifest_path.to_path_buf(),
86 context_summary: description,
87 public_api_files: Vec::new(),
88 internal_files: Vec::new(),
89 content_hash: [0u8; 32],
90 };
91
92 Ok((project, deps))
93 }
94}
95
96fn parse_cargo_dep(
97 name: &str,
98 value: &toml::Value,
99 kind: &DepKind,
100 workspace_root: &Path,
101) -> Dependency {
102 let mut version_req = String::new();
103 let mut is_workspace = false;
104 let mut resolved_path = None;
105
106 match value {
107 toml::Value::String(v) => {
108 version_req = v.clone();
109 }
110 toml::Value::Table(t) => {
111 if let Some(v) = t.get("version").and_then(|v| v.as_str()) {
112 version_req = v.to_string();
113 }
114 if let Some(true) = t.get("workspace").and_then(|v| v.as_bool()) {
115 is_workspace = true;
116 version_req = "workspace".to_string();
117 }
118 if let Some(p) = t.get("path").and_then(|v| v.as_str()) {
119 resolved_path = Some(workspace_root.join(p));
120 }
121 }
122 _ => {}
123 }
124
125 Dependency {
126 name: name.to_string(),
127 version_req,
128 kind: kind.clone(),
129 is_workspace,
130 resolved_path,
131 }
132}