Skip to main content

lean_ctx/core/patterns/
deps_cmd.rs

1use std::path::Path;
2
3pub fn compress(path: &str) -> Option<String> {
4    let content = std::fs::read_to_string(path).ok()?;
5    let filename = Path::new(path)
6        .file_name()
7        .and_then(|n| n.to_str())
8        .unwrap_or(path);
9
10    match filename {
11        "package.json" => compress_package_json(&content),
12        "Cargo.toml" => compress_cargo_toml(&content),
13        "requirements.txt" => compress_requirements(&content),
14        "go.mod" => compress_go_mod(&content),
15        "Gemfile" => compress_gemfile(&content),
16        "pyproject.toml" => compress_pyproject(&content),
17        _ => None,
18    }
19}
20
21pub fn detect_and_compress(dir: &str) -> Option<String> {
22    let candidates = [
23        "package.json",
24        "Cargo.toml",
25        "requirements.txt",
26        "go.mod",
27        "Gemfile",
28        "pyproject.toml",
29    ];
30
31    for name in &candidates {
32        let path = format!("{}/{}", dir.trim_end_matches('/'), name);
33        if Path::new(&path).exists() {
34            if let Some(result) = compress(&path) {
35                return Some(result);
36            }
37        }
38    }
39
40    None
41}
42
43fn compress_package_json(content: &str) -> Option<String> {
44    let val: serde_json::Value = serde_json::from_str(content).ok()?;
45    let obj = val.as_object()?;
46
47    let name = obj.get("name").and_then(|v| v.as_str()).unwrap_or("?");
48    let version = obj.get("version").and_then(|v| v.as_str()).unwrap_or("?");
49
50    let mut result = format!("Node ({name}@{version}):");
51
52    if let Some(deps) = obj.get("dependencies").and_then(|v| v.as_object()) {
53        let dep_list: Vec<String> = deps
54            .iter()
55            .map(|(k, v)| format!("{k} ({})", v.as_str().unwrap_or("?")))
56            .collect();
57        result.push_str(&format!(
58            "\n  deps ({}): {}",
59            dep_list.len(),
60            dep_list.join(", ")
61        ));
62    }
63
64    if let Some(deps) = obj.get("devDependencies").and_then(|v| v.as_object()) {
65        let dep_list: Vec<String> = deps
66            .iter()
67            .take(10)
68            .map(|(k, v)| format!("{k} ({})", v.as_str().unwrap_or("?")))
69            .collect();
70        let suffix = if deps.len() > 10 {
71            format!(", ... +{} more", deps.len() - 10)
72        } else {
73            String::new()
74        };
75        result.push_str(&format!(
76            "\n  devDeps ({}): {}{}",
77            deps.len(),
78            dep_list.join(", "),
79            suffix
80        ));
81    }
82
83    Some(result)
84}
85
86fn compress_cargo_toml(content: &str) -> Option<String> {
87    let mut name = String::new();
88    let mut version = String::new();
89    let mut deps = Vec::new();
90    let mut in_deps = false;
91
92    for line in content.lines() {
93        let trimmed = line.trim();
94
95        if trimmed.starts_with("[dependencies]") {
96            in_deps = true;
97            continue;
98        }
99        if trimmed.starts_with('[') && in_deps {
100            in_deps = false;
101        }
102
103        if trimmed.starts_with("name") {
104            if let Some(v) = extract_toml_string(trimmed) {
105                name = v;
106            }
107        }
108        if trimmed.starts_with("version") && !in_deps {
109            if let Some(v) = extract_toml_string(trimmed) {
110                version = v;
111            }
112        }
113
114        if in_deps && !trimmed.is_empty() && !trimmed.starts_with('#') {
115            if let Some(dep_name) = trimmed.split('=').next() {
116                deps.push(dep_name.trim().to_string());
117            }
118        }
119    }
120
121    if deps.is_empty() {
122        return None;
123    }
124
125    let mut result = format!("Rust ({name}@{version}):");
126    result.push_str(&format!("\n  deps ({}): {}", deps.len(), deps.join(", ")));
127
128    Some(result)
129}
130
131fn compress_requirements(content: &str) -> Option<String> {
132    let deps: Vec<&str> = content
133        .lines()
134        .filter(|l| !l.trim().is_empty() && !l.trim().starts_with('#'))
135        .collect();
136
137    if deps.is_empty() {
138        return None;
139    }
140
141    let short: Vec<String> = deps
142        .iter()
143        .map(|d| {
144            d.split("==")
145                .next()
146                .unwrap_or(d)
147                .split(">=")
148                .next()
149                .unwrap_or(d)
150                .trim()
151                .to_string()
152        })
153        .collect();
154
155    Some(format!(
156        "Python:\n  deps ({}): {}",
157        short.len(),
158        short.join(", ")
159    ))
160}
161
162fn compress_go_mod(content: &str) -> Option<String> {
163    let mut module = String::new();
164    let mut deps = Vec::new();
165    let mut in_require = false;
166
167    for line in content.lines() {
168        let trimmed = line.trim();
169
170        if trimmed.starts_with("module ") {
171            module = trimmed.strip_prefix("module ").unwrap_or("").to_string();
172        }
173        if trimmed == "require (" {
174            in_require = true;
175            continue;
176        }
177        if trimmed == ")" && in_require {
178            in_require = false;
179        }
180        if in_require && !trimmed.is_empty() {
181            if let Some(name) = trimmed.split_whitespace().next() {
182                if !name.contains("// indirect") {
183                    let short = name.rsplit('/').next().unwrap_or(name);
184                    deps.push(short.to_string());
185                }
186            }
187        }
188    }
189
190    if deps.is_empty() {
191        return None;
192    }
193
194    Some(format!(
195        "Go ({module}):\n  deps ({}): {}",
196        deps.len(),
197        deps.join(", ")
198    ))
199}
200
201fn compress_gemfile(content: &str) -> Option<String> {
202    let gems: Vec<&str> = content
203        .lines()
204        .filter(|l| l.trim().starts_with("gem "))
205        .map(|l| {
206            l.trim()
207                .strip_prefix("gem ")
208                .unwrap_or(l)
209                .split(',')
210                .next()
211                .unwrap_or("")
212                .trim()
213                .trim_matches('\'')
214                .trim_matches('"')
215        })
216        .collect();
217
218    if gems.is_empty() {
219        return None;
220    }
221
222    Some(format!(
223        "Ruby:\n  gems ({}): {}",
224        gems.len(),
225        gems.join(", ")
226    ))
227}
228
229fn compress_pyproject(content: &str) -> Option<String> {
230    let mut deps = Vec::new();
231    let mut in_deps = false;
232
233    for line in content.lines() {
234        let trimmed = line.trim();
235        if trimmed == "dependencies = [" || trimmed.starts_with("dependencies") {
236            in_deps = true;
237            continue;
238        }
239        if trimmed == "]" && in_deps {
240            in_deps = false;
241        }
242        if in_deps {
243            let clean = trimmed.trim_matches(|c: char| c == '"' || c == '\'' || c == ',');
244            let name = clean
245                .split(">=")
246                .next()
247                .unwrap_or(clean)
248                .split("==")
249                .next()
250                .unwrap_or(clean)
251                .trim();
252            if !name.is_empty() {
253                deps.push(name.to_string());
254            }
255        }
256    }
257
258    if deps.is_empty() {
259        return None;
260    }
261
262    Some(format!(
263        "Python:\n  deps ({}): {}",
264        deps.len(),
265        deps.join(", ")
266    ))
267}
268
269fn extract_toml_string(line: &str) -> Option<String> {
270    let after_eq = line.split('=').nth(1)?.trim();
271    Some(after_eq.trim_matches('"').to_string())
272}