Skip to main content

lean_ctx/tools/
ctx_execute.rs

1use crate::core::sandbox::{self, SandboxResult};
2use crate::core::tokens::count_tokens;
3
4pub fn handle(language: &str, code: &str, intent: Option<&str>, timeout: Option<u64>) -> String {
5    let result = sandbox::execute(language, code, timeout);
6    format_result(&result, intent)
7}
8
9pub fn handle_file(path: &str, intent: Option<&str>) -> String {
10    let content = match std::fs::read_to_string(path) {
11        Ok(c) => c,
12        Err(e) => return format!("Error reading {path}: {e}"),
13    };
14
15    let language = detect_language_from_extension(path);
16    let code = build_file_processing_script(&language, &content, intent);
17    let result = sandbox::execute(&language, &code, None);
18    format_result(&result, intent)
19}
20
21pub fn handle_batch(items: &[(String, String)]) -> String {
22    let results = sandbox::batch_execute(items);
23    let mut output = Vec::new();
24
25    for (i, result) in results.iter().enumerate() {
26        let label = format!("[{}/{}] {}", i + 1, results.len(), result.language);
27        if result.exit_code == 0 {
28            let stdout = result.stdout.trim();
29            if stdout.is_empty() {
30                output.push(format!("{label}: (no output) [{} ms]", result.duration_ms));
31            } else {
32                output.push(format!("{label}: {stdout} [{} ms]", result.duration_ms));
33            }
34        } else {
35            let stderr = result.stderr.trim();
36            output.push(format!(
37                "{label}: EXIT {} — {stderr} [{} ms]",
38                result.exit_code, result.duration_ms
39            ));
40        }
41    }
42
43    let total_ms: u64 = results.iter().map(|r| r.duration_ms).sum();
44    output.push(format!("\n{} tasks, {} ms total", results.len(), total_ms));
45    output.join("\n")
46}
47
48fn format_result(result: &SandboxResult, intent: Option<&str>) -> String {
49    let mut parts = Vec::new();
50
51    if result.exit_code == 0 {
52        let stdout = result.stdout.trim();
53        if stdout.is_empty() {
54            parts.push("(no output)".to_string());
55        } else {
56            let raw_tokens = count_tokens(stdout);
57            parts.push(stdout.to_string());
58
59            if let Some(intent_desc) = intent {
60                if raw_tokens > 50 {
61                    parts.push(format!("[intent: {intent_desc}]"));
62                }
63            }
64        }
65    } else {
66        if !result.stdout.is_empty() {
67            parts.push(result.stdout.trim().to_string());
68        }
69        parts.push(format!(
70            "EXIT {} — {}",
71            result.exit_code,
72            result.stderr.trim()
73        ));
74    }
75
76    parts.push(format!("[{} | {} ms]", result.language, result.duration_ms));
77    parts.join("\n")
78}
79
80fn detect_language_from_extension(path: &str) -> String {
81    let ext = path.rsplit('.').next().unwrap_or("");
82    match ext {
83        "js" | "mjs" | "cjs" => "javascript",
84        "ts" | "mts" | "cts" => "typescript",
85        "py" => "python",
86        "sh" | "bash" => "shell",
87        "rb" => "ruby",
88        "go" => "go",
89        "rs" => "rust",
90        "php" => "php",
91        "pl" => "perl",
92        "r" | "R" => "r",
93        "ex" | "exs" => "elixir",
94        "json" | "csv" | "log" | "txt" | "xml" | "yaml" | "yml" | "md" | "html" => "python",
95        _ => "shell",
96    }
97    .to_string()
98}
99
100fn build_file_processing_script(language: &str, content: &str, intent: Option<&str>) -> String {
101    let escaped = content.replace('\\', "\\\\").replace('\'', "\\'");
102    let intent_str = intent.unwrap_or("summarize the content");
103
104    match language {
105        "python" => {
106            format!(
107                r#"
108import json, re
109from collections import Counter
110
111data = '''{escaped}'''
112
113lines = data.strip().split('\n')
114total_lines = len(lines)
115total_bytes = len(data.encode('utf-8'))
116
117word_count = sum(len(line.split()) for line in lines)
118
119print(f"{{total_lines}} lines, {{total_bytes}} bytes, {{word_count}} words")
120print(f"Intent: {intent_str}")
121
122if total_lines > 10:
123    print(f"First 3: {{lines[:3]}}")
124    print(f"Last 3: {{lines[-3:]}}")
125"#
126            )
127        }
128        _ => {
129            format!(
130                r#"
131data='{escaped}'
132lines=$(echo "$data" | wc -l | tr -d ' ')
133bytes=$(echo "$data" | wc -c | tr -d ' ')
134echo "$lines lines, $bytes bytes"
135echo "Intent: {intent_str}"
136echo "$data" | head -3
137echo "..."
138echo "$data" | tail -3
139"#
140            )
141        }
142    }
143}
144
145#[cfg(test)]
146mod tests {
147    use super::*;
148
149    #[test]
150    fn handle_simple_python() {
151        let result = handle("python", "print(2 + 2)", None, None);
152        assert!(result.contains("4"));
153        assert!(result.contains("python"));
154    }
155
156    #[test]
157    fn handle_with_intent() {
158        let result = handle(
159            "python",
160            "print('found 5 errors')",
161            Some("count errors"),
162            None,
163        );
164        assert!(result.contains("found 5 errors"));
165    }
166
167    #[test]
168    fn handle_error_shows_stderr() {
169        let result = handle("python", "raise Exception('boom')", None, None);
170        assert!(result.contains("EXIT"));
171        assert!(result.contains("boom"));
172    }
173
174    #[test]
175    fn detect_language_from_path() {
176        assert_eq!(detect_language_from_extension("test.py"), "python");
177        assert_eq!(detect_language_from_extension("test.js"), "javascript");
178        assert_eq!(detect_language_from_extension("test.rs"), "rust");
179        assert_eq!(detect_language_from_extension("test.csv"), "python");
180        assert_eq!(detect_language_from_extension("test.log"), "python");
181    }
182
183    #[test]
184    #[cfg(not(target_os = "windows"))]
185    fn batch_multiple_tasks() {
186        let items = vec![
187            ("python".to_string(), "print('task1')".to_string()),
188            ("shell".to_string(), "echo task2".to_string()),
189        ];
190        let result = handle_batch(&items);
191        assert!(result.contains("task1"));
192        assert!(result.contains("task2"));
193        assert!(result.contains("2 tasks"));
194    }
195}