grok-cli-acp 0.1.2
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.
- package/.env.example +42 -0
- package/.github/workflows/ci.yml +30 -0
- package/.github/workflows/rust.yml +22 -0
- package/.grok/.env.example +85 -0
- package/.grok/COMPLETE_FIX_SUMMARY.md +466 -0
- package/.grok/ENV_CONFIG_GUIDE.md +173 -0
- package/.grok/QUICK_REFERENCE.md +180 -0
- package/.grok/README.md +104 -0
- package/.grok/TESTING_GUIDE.md +393 -0
- package/CHANGELOG.md +465 -0
- package/CODE_REVIEW_SUMMARY.md +414 -0
- package/COMPLETE_FIX_SUMMARY.md +415 -0
- package/CONFIGURATION.md +489 -0
- package/CONTEXT_FILES_GUIDE.md +419 -0
- package/CONTRIBUTING.md +55 -0
- package/CURSOR_POSITION_FIX.md +206 -0
- package/Cargo.toml +88 -0
- package/ERROR_HANDLING_REPORT.md +361 -0
- package/FINAL_FIX_SUMMARY.md +462 -0
- package/FIXES.md +37 -0
- package/FIXES_SUMMARY.md +87 -0
- package/GROK_API_MIGRATION_SUMMARY.md +111 -0
- package/LICENSE +22 -0
- package/MIGRATION_TO_GROK_API.md +223 -0
- package/README.md +504 -0
- package/REVIEW_COMPLETE.md +416 -0
- package/REVIEW_QUICK_REFERENCE.md +173 -0
- package/SECURITY.md +463 -0
- package/SECURITY_AUDIT.md +661 -0
- package/SETUP.md +287 -0
- package/TESTING_TOOLS.md +88 -0
- package/TESTING_TOOL_EXECUTION.md +239 -0
- package/TOOL_EXECUTION_FIX.md +491 -0
- package/VERIFICATION_CHECKLIST.md +419 -0
- package/docs/API.md +74 -0
- package/docs/CHAT_LOGGING.md +39 -0
- package/docs/CURSOR_FIX_DEMO.md +306 -0
- package/docs/ERROR_HANDLING_GUIDE.md +547 -0
- package/docs/FILE_OPERATIONS.md +449 -0
- package/docs/INTERACTIVE.md +401 -0
- package/docs/PROJECT_CREATION_GUIDE.md +570 -0
- package/docs/QUICKSTART.md +378 -0
- package/docs/QUICK_REFERENCE.md +691 -0
- package/docs/RELEASE_NOTES_0.1.2.md +240 -0
- package/docs/TOOLS.md +459 -0
- package/docs/TOOLS_QUICK_REFERENCE.md +210 -0
- package/docs/ZED_INTEGRATION.md +371 -0
- package/docs/extensions.md +464 -0
- package/docs/settings.md +293 -0
- package/examples/extensions/logging-hook/README.md +91 -0
- package/examples/extensions/logging-hook/extension.json +22 -0
- package/package.json +30 -0
- package/scripts/test_acp.py +252 -0
- package/scripts/test_acp.sh +143 -0
- package/scripts/test_acp_simple.sh +72 -0
- package/src/acp/mod.rs +741 -0
- package/src/acp/protocol.rs +323 -0
- package/src/acp/security.rs +298 -0
- package/src/acp/tools.rs +697 -0
- package/src/bin/banner_demo.rs +216 -0
- package/src/bin/docgen.rs +18 -0
- package/src/bin/installer.rs +217 -0
- package/src/cli/app.rs +310 -0
- package/src/cli/commands/acp.rs +721 -0
- package/src/cli/commands/chat.rs +485 -0
- package/src/cli/commands/code.rs +513 -0
- package/src/cli/commands/config.rs +394 -0
- package/src/cli/commands/health.rs +442 -0
- package/src/cli/commands/history.rs +421 -0
- package/src/cli/commands/mod.rs +14 -0
- package/src/cli/commands/settings.rs +1384 -0
- package/src/cli/mod.rs +166 -0
- package/src/config/mod.rs +2212 -0
- package/src/display/ascii_art.rs +139 -0
- package/src/display/banner.rs +289 -0
- package/src/display/components/input.rs +323 -0
- package/src/display/components/mod.rs +2 -0
- package/src/display/components/settings_list.rs +306 -0
- package/src/display/interactive.rs +1255 -0
- package/src/display/mod.rs +62 -0
- package/src/display/terminal.rs +42 -0
- package/src/display/tips.rs +316 -0
- package/src/grok_client_ext.rs +177 -0
- package/src/hooks/loader.rs +407 -0
- package/src/hooks/mod.rs +158 -0
- package/src/lib.rs +174 -0
- package/src/main.rs +65 -0
- package/src/mcp/client.rs +195 -0
- package/src/mcp/config.rs +20 -0
- package/src/mcp/mod.rs +6 -0
- package/src/mcp/protocol.rs +67 -0
- package/src/utils/auth.rs +41 -0
- package/src/utils/chat_logger.rs +568 -0
- package/src/utils/context.rs +390 -0
- package/src/utils/mod.rs +16 -0
- package/src/utils/network.rs +320 -0
- package/src/utils/rate_limiter.rs +166 -0
- package/src/utils/session.rs +73 -0
- package/src/utils/shell_permissions.rs +389 -0
- package/src/utils/telemetry.rs +41 -0
|
@@ -0,0 +1,513 @@
|
|
|
1
|
+
//! Code command handler for grok-cli
|
|
2
|
+
//!
|
|
3
|
+
//! Handles code-related operations including explanation, review, generation,
|
|
4
|
+
//! and fixing code issues using Grok AI.
|
|
5
|
+
|
|
6
|
+
use anyhow::{anyhow, Result};
|
|
7
|
+
use colored::*;
|
|
8
|
+
use std::fs;
|
|
9
|
+
use std::path::Path;
|
|
10
|
+
|
|
11
|
+
use crate::GrokClient;
|
|
12
|
+
use crate::cli::{
|
|
13
|
+
create_spinner, format_code, print_error, print_info, print_success, print_warning,
|
|
14
|
+
};
|
|
15
|
+
use crate::config::RateLimitConfig;
|
|
16
|
+
use crate::CodeAction;
|
|
17
|
+
|
|
18
|
+
/// Handle code-related commands
|
|
19
|
+
pub async fn handle_code_action(
|
|
20
|
+
action: CodeAction,
|
|
21
|
+
api_key: &str,
|
|
22
|
+
model: &str,
|
|
23
|
+
timeout_secs: u64,
|
|
24
|
+
max_retries: u32,
|
|
25
|
+
rate_limit_config: RateLimitConfig,
|
|
26
|
+
) -> Result<()> {
|
|
27
|
+
let client = GrokClient::with_settings(api_key, timeout_secs, max_retries)?
|
|
28
|
+
.with_rate_limits(rate_limit_config);
|
|
29
|
+
|
|
30
|
+
match action {
|
|
31
|
+
CodeAction::Explain { input, file } => {
|
|
32
|
+
handle_code_explain(client, &input, file, model).await
|
|
33
|
+
}
|
|
34
|
+
CodeAction::Review { input, file, focus } => {
|
|
35
|
+
handle_code_review(client, &input, file, focus.as_deref(), model).await
|
|
36
|
+
}
|
|
37
|
+
CodeAction::Generate {
|
|
38
|
+
description,
|
|
39
|
+
language,
|
|
40
|
+
output,
|
|
41
|
+
} => {
|
|
42
|
+
handle_code_generate(
|
|
43
|
+
client,
|
|
44
|
+
description,
|
|
45
|
+
language.as_deref(),
|
|
46
|
+
output.as_deref(),
|
|
47
|
+
model,
|
|
48
|
+
)
|
|
49
|
+
.await
|
|
50
|
+
}
|
|
51
|
+
CodeAction::Fix { file, issue } => handle_code_fix(client, &file, issue, model).await,
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/// Handle code explanation requests
|
|
56
|
+
async fn handle_code_explain(
|
|
57
|
+
client: GrokClient,
|
|
58
|
+
input: &str,
|
|
59
|
+
is_file: bool,
|
|
60
|
+
model: &str,
|
|
61
|
+
) -> Result<()> {
|
|
62
|
+
let (code, language) = if is_file || Path::new(input).exists() {
|
|
63
|
+
print_info(&format!("Reading code from file: {}", input));
|
|
64
|
+
let code = fs::read_to_string(input)
|
|
65
|
+
.map_err(|e| anyhow!("Failed to read file '{}': {}", input, e))?;
|
|
66
|
+
|
|
67
|
+
let language = detect_language_from_path(input);
|
|
68
|
+
(code, language)
|
|
69
|
+
} else {
|
|
70
|
+
(input.to_string(), None)
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
if code.trim().is_empty() {
|
|
74
|
+
return Err(anyhow!("No code provided to explain"));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
print_info(&format!("Explaining code using model: {}", model));
|
|
78
|
+
|
|
79
|
+
let spinner = create_spinner("Analyzing code...");
|
|
80
|
+
|
|
81
|
+
let language_hint = language
|
|
82
|
+
.as_ref()
|
|
83
|
+
.map(|l| format!(" (detected language: {})", l))
|
|
84
|
+
.unwrap_or_default();
|
|
85
|
+
|
|
86
|
+
let system_prompt = "You are an expert software engineer and teacher. Your task is to explain code in a clear, educational manner. Focus on:
|
|
87
|
+
- What the code does (high-level purpose)
|
|
88
|
+
- How it works (step-by-step breakdown)
|
|
89
|
+
- Key programming concepts and patterns used
|
|
90
|
+
- Important details about the implementation
|
|
91
|
+
- Potential improvements or alternatives
|
|
92
|
+
|
|
93
|
+
Make your explanation accessible but thorough. Use examples when helpful.";
|
|
94
|
+
|
|
95
|
+
let user_message = format!(
|
|
96
|
+
"Please explain this code{}:\n\n```\n{}\n```",
|
|
97
|
+
language_hint, code
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
let response = client
|
|
101
|
+
.chat_completion(&user_message, Some(system_prompt), 0.3, 4096, model)
|
|
102
|
+
.await;
|
|
103
|
+
|
|
104
|
+
spinner.finish_and_clear();
|
|
105
|
+
|
|
106
|
+
match response {
|
|
107
|
+
Ok(explanation) => {
|
|
108
|
+
print_success("Code explanation generated!");
|
|
109
|
+
println!();
|
|
110
|
+
println!("{}", "📖 Code Explanation:".cyan().bold());
|
|
111
|
+
println!("{}", "═".repeat(50));
|
|
112
|
+
println!("{}", explanation);
|
|
113
|
+
|
|
114
|
+
if let Some(lang) = language {
|
|
115
|
+
println!();
|
|
116
|
+
println!("{}", format!("Language: {}", lang).dimmed());
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
Err(e) => {
|
|
120
|
+
print_error(&format!("Failed to generate explanation: {}", e));
|
|
121
|
+
return Err(e);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
Ok(())
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/// Handle code review requests
|
|
129
|
+
async fn handle_code_review(
|
|
130
|
+
client: GrokClient,
|
|
131
|
+
input: &str,
|
|
132
|
+
is_file: bool,
|
|
133
|
+
focus: Option<&str>,
|
|
134
|
+
model: &str,
|
|
135
|
+
) -> Result<()> {
|
|
136
|
+
let (code, language) = if is_file || Path::new(input).exists() {
|
|
137
|
+
print_info(&format!("Reading code from file: {}", input));
|
|
138
|
+
let code = fs::read_to_string(input)
|
|
139
|
+
.map_err(|e| anyhow!("Failed to read file '{}': {}", input, e))?;
|
|
140
|
+
|
|
141
|
+
let language = detect_language_from_path(input);
|
|
142
|
+
(code, language)
|
|
143
|
+
} else {
|
|
144
|
+
(input.to_string(), None)
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
if code.trim().is_empty() {
|
|
148
|
+
return Err(anyhow!("No code provided to review"));
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
print_info(&format!("Reviewing code using model: {}", model));
|
|
152
|
+
|
|
153
|
+
let focus_areas = focus.unwrap_or("security, performance, style, bugs, maintainability");
|
|
154
|
+
print_info(&format!("Focus areas: {}", focus_areas));
|
|
155
|
+
|
|
156
|
+
let spinner = create_spinner("Reviewing code...");
|
|
157
|
+
|
|
158
|
+
let language_hint = language
|
|
159
|
+
.as_ref()
|
|
160
|
+
.map(|l| format!(" (language: {})", l))
|
|
161
|
+
.unwrap_or_default();
|
|
162
|
+
|
|
163
|
+
let system_prompt = format!(
|
|
164
|
+
"You are an expert code reviewer with years of experience in software development. \
|
|
165
|
+
Review the provided code focusing on: {}. \n\n\
|
|
166
|
+
Provide a comprehensive review covering:\n\
|
|
167
|
+
- Issues and potential bugs\n\
|
|
168
|
+
- Security vulnerabilities\n\
|
|
169
|
+
- Performance improvements\n\
|
|
170
|
+
- Code style and best practices\n\
|
|
171
|
+
- Maintainability concerns\n\
|
|
172
|
+
- Suggestions for improvement\n\n\
|
|
173
|
+
Be specific, actionable, and constructive in your feedback. \
|
|
174
|
+
Use examples when suggesting improvements.",
|
|
175
|
+
focus_areas
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
let user_message = format!(
|
|
179
|
+
"Please review this code{}:\n\n```\n{}\n```",
|
|
180
|
+
language_hint, code
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
let response = client
|
|
184
|
+
.chat_completion(&user_message, Some(&system_prompt), 0.2, 6144, model)
|
|
185
|
+
.await;
|
|
186
|
+
|
|
187
|
+
spinner.finish_and_clear();
|
|
188
|
+
|
|
189
|
+
match response {
|
|
190
|
+
Ok(review) => {
|
|
191
|
+
print_success("Code review completed!");
|
|
192
|
+
println!();
|
|
193
|
+
println!("{}", "🔍 Code Review:".cyan().bold());
|
|
194
|
+
println!("{}", "═".repeat(50));
|
|
195
|
+
println!("{}", review);
|
|
196
|
+
|
|
197
|
+
if let Some(lang) = language {
|
|
198
|
+
println!();
|
|
199
|
+
println!("{}", format!("Language: {}", lang).dimmed());
|
|
200
|
+
}
|
|
201
|
+
println!("{}", format!("Focus areas: {}", focus_areas).dimmed());
|
|
202
|
+
}
|
|
203
|
+
Err(e) => {
|
|
204
|
+
print_error(&format!("Failed to generate review: {}", e));
|
|
205
|
+
return Err(e);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
Ok(())
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/// Handle code generation requests
|
|
213
|
+
async fn handle_code_generate(
|
|
214
|
+
client: GrokClient,
|
|
215
|
+
description: Vec<String>,
|
|
216
|
+
language: Option<&str>,
|
|
217
|
+
output_file: Option<&str>,
|
|
218
|
+
model: &str,
|
|
219
|
+
) -> Result<()> {
|
|
220
|
+
let combined_description = description.join(" ");
|
|
221
|
+
|
|
222
|
+
if combined_description.trim().is_empty() {
|
|
223
|
+
return Err(anyhow!("No description provided for code generation"));
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
let target_language = language.unwrap_or("Python");
|
|
227
|
+
print_info(&format!(
|
|
228
|
+
"Generating {} code using model: {}",
|
|
229
|
+
target_language, model
|
|
230
|
+
));
|
|
231
|
+
|
|
232
|
+
let spinner = create_spinner("Generating code...");
|
|
233
|
+
|
|
234
|
+
let system_prompt = format!(
|
|
235
|
+
"You are an expert {} developer. Generate clean, well-documented, and production-ready code \
|
|
236
|
+
based on the user's requirements. Follow these guidelines:\n\
|
|
237
|
+
- Write clear, readable code with appropriate comments\n\
|
|
238
|
+
- Follow {} best practices and conventions\n\
|
|
239
|
+
- Include error handling where appropriate\n\
|
|
240
|
+
- Use meaningful variable and function names\n\
|
|
241
|
+
- Add docstrings/documentation comments\n\
|
|
242
|
+
- Consider edge cases and validation\n\n\
|
|
243
|
+
Provide only the code with minimal explanation unless asked for more detail.",
|
|
244
|
+
target_language, target_language
|
|
245
|
+
);
|
|
246
|
+
|
|
247
|
+
let user_message = format!(
|
|
248
|
+
"Generate {} code for the following requirements:\n\n{}",
|
|
249
|
+
target_language, combined_description
|
|
250
|
+
);
|
|
251
|
+
|
|
252
|
+
let response = client
|
|
253
|
+
.chat_completion(&user_message, Some(&system_prompt), 0.1, 8192, model)
|
|
254
|
+
.await;
|
|
255
|
+
|
|
256
|
+
spinner.finish_and_clear();
|
|
257
|
+
|
|
258
|
+
match response {
|
|
259
|
+
Ok(generated_code) => {
|
|
260
|
+
print_success("Code generated successfully!");
|
|
261
|
+
println!();
|
|
262
|
+
println!(
|
|
263
|
+
"{}",
|
|
264
|
+
format!("🚀 Generated {} Code:", target_language)
|
|
265
|
+
.cyan()
|
|
266
|
+
.bold()
|
|
267
|
+
);
|
|
268
|
+
println!("{}", "═".repeat(50));
|
|
269
|
+
|
|
270
|
+
// Try to extract just the code from the response
|
|
271
|
+
let clean_code = extract_code_from_response(&generated_code);
|
|
272
|
+
println!("{}", format_code(&clean_code, Some(target_language)));
|
|
273
|
+
|
|
274
|
+
// Save to file if requested
|
|
275
|
+
if let Some(output_path) = output_file {
|
|
276
|
+
match save_code_to_file(&clean_code, output_path) {
|
|
277
|
+
Ok(()) => {
|
|
278
|
+
print_success(&format!("Code saved to: {}", output_path));
|
|
279
|
+
}
|
|
280
|
+
Err(e) => {
|
|
281
|
+
print_warning(&format!("Failed to save to file: {}", e));
|
|
282
|
+
print_info("The generated code is displayed above.");
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
println!();
|
|
288
|
+
println!("{}", format!("Language: {}", target_language).dimmed());
|
|
289
|
+
println!(
|
|
290
|
+
"{}",
|
|
291
|
+
format!("Requirements: {}", combined_description).dimmed()
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
Err(e) => {
|
|
295
|
+
print_error(&format!("Failed to generate code: {}", e));
|
|
296
|
+
return Err(e);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
Ok(())
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/// Handle code fixing requests
|
|
304
|
+
async fn handle_code_fix(
|
|
305
|
+
client: GrokClient,
|
|
306
|
+
file_path: &str,
|
|
307
|
+
issue_description: Vec<String>,
|
|
308
|
+
model: &str,
|
|
309
|
+
) -> Result<()> {
|
|
310
|
+
let combined_issue = issue_description.join(" ");
|
|
311
|
+
|
|
312
|
+
if combined_issue.trim().is_empty() {
|
|
313
|
+
return Err(anyhow!("No issue description provided"));
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
print_info(&format!("Reading code from file: {}", file_path));
|
|
317
|
+
|
|
318
|
+
let code = fs::read_to_string(file_path)
|
|
319
|
+
.map_err(|e| anyhow!("Failed to read file '{}': {}", file_path, e))?;
|
|
320
|
+
|
|
321
|
+
if code.trim().is_empty() {
|
|
322
|
+
return Err(anyhow!("File is empty: {}", file_path));
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
let language = detect_language_from_path(file_path);
|
|
326
|
+
print_info(&format!("Fixing code using model: {}", model));
|
|
327
|
+
|
|
328
|
+
if let Some(ref lang) = language {
|
|
329
|
+
print_info(&format!("Detected language: {}", lang));
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
let spinner = create_spinner("Analyzing and fixing code...");
|
|
333
|
+
|
|
334
|
+
let language_hint = language
|
|
335
|
+
.as_ref()
|
|
336
|
+
.map(|l| format!(" (language: {})", l))
|
|
337
|
+
.unwrap_or_default();
|
|
338
|
+
|
|
339
|
+
let system_prompt =
|
|
340
|
+
"You are an expert software engineer specializing in debugging and code fixes. \
|
|
341
|
+
Your task is to analyze the provided code and fix the described issue. \n\n\
|
|
342
|
+
Guidelines:\n\
|
|
343
|
+
- Understand the issue thoroughly\n\
|
|
344
|
+
- Identify the root cause\n\
|
|
345
|
+
- Provide a complete, corrected version of the code\n\
|
|
346
|
+
- Explain what was wrong and how you fixed it\n\
|
|
347
|
+
- Ensure the fix doesn't introduce new issues\n\
|
|
348
|
+
- Maintain the original code structure and style when possible\n\n\
|
|
349
|
+
Format your response with:\n\
|
|
350
|
+
1. Brief explanation of the issue\n\
|
|
351
|
+
2. The corrected code\n\
|
|
352
|
+
3. Explanation of the changes made"
|
|
353
|
+
.to_string();
|
|
354
|
+
|
|
355
|
+
let user_message = format!(
|
|
356
|
+
"Here is the code{} that needs to be fixed:\n\n```\n{}\n```\n\n\
|
|
357
|
+
Issue to fix: {}",
|
|
358
|
+
language_hint, code, combined_issue
|
|
359
|
+
);
|
|
360
|
+
|
|
361
|
+
let response = client
|
|
362
|
+
.chat_completion(&user_message, Some(&system_prompt), 0.1, 8192, model)
|
|
363
|
+
.await;
|
|
364
|
+
|
|
365
|
+
spinner.finish_and_clear();
|
|
366
|
+
|
|
367
|
+
match response {
|
|
368
|
+
Ok(fix_response) => {
|
|
369
|
+
print_success("Code analysis and fix completed!");
|
|
370
|
+
println!();
|
|
371
|
+
println!("{}", "🔧 Code Fix:".cyan().bold());
|
|
372
|
+
println!("{}", "═".repeat(50));
|
|
373
|
+
println!("{}", fix_response);
|
|
374
|
+
|
|
375
|
+
println!();
|
|
376
|
+
println!("{}", format!("File: {}", file_path).dimmed());
|
|
377
|
+
println!("{}", format!("Issue: {}", combined_issue).dimmed());
|
|
378
|
+
|
|
379
|
+
if let Some(lang) = language {
|
|
380
|
+
println!("{}", format!("Language: {}", lang).dimmed());
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
println!();
|
|
384
|
+
print_warning(
|
|
385
|
+
"⚠️ Please review the suggested fix carefully before applying it to your code.",
|
|
386
|
+
);
|
|
387
|
+
}
|
|
388
|
+
Err(e) => {
|
|
389
|
+
print_error(&format!("Failed to fix code: {}", e));
|
|
390
|
+
return Err(e);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
Ok(())
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/// Detect programming language from file extension
|
|
398
|
+
fn detect_language_from_path(path: &str) -> Option<String> {
|
|
399
|
+
let extension = Path::new(path).extension()?.to_str()?.to_lowercase();
|
|
400
|
+
|
|
401
|
+
let language = match extension.as_str() {
|
|
402
|
+
"rs" => "Rust",
|
|
403
|
+
"py" => "Python",
|
|
404
|
+
"js" => "JavaScript",
|
|
405
|
+
"ts" | "tsx" => "TypeScript",
|
|
406
|
+
"java" => "Java",
|
|
407
|
+
"cpp" | "cc" | "cxx" => "C++",
|
|
408
|
+
"c" => "C",
|
|
409
|
+
"cs" => "C#",
|
|
410
|
+
"go" => "Go",
|
|
411
|
+
"php" => "PHP",
|
|
412
|
+
"rb" => "Ruby",
|
|
413
|
+
"swift" => "Swift",
|
|
414
|
+
"kt" => "Kotlin",
|
|
415
|
+
"scala" => "Scala",
|
|
416
|
+
"hs" => "Haskell",
|
|
417
|
+
"ml" => "OCaml",
|
|
418
|
+
"clj" => "Clojure",
|
|
419
|
+
"elm" => "Elm",
|
|
420
|
+
"dart" => "Dart",
|
|
421
|
+
"lua" => "Lua",
|
|
422
|
+
"r" => "R",
|
|
423
|
+
"m" => "Objective-C",
|
|
424
|
+
"sh" | "bash" => "Shell",
|
|
425
|
+
"sql" => "SQL",
|
|
426
|
+
"html" => "HTML",
|
|
427
|
+
"css" => "CSS",
|
|
428
|
+
"scss" | "sass" => "SCSS",
|
|
429
|
+
"json" => "JSON",
|
|
430
|
+
"yaml" | "yml" => "YAML",
|
|
431
|
+
"toml" => "TOML",
|
|
432
|
+
"xml" => "XML",
|
|
433
|
+
"md" => "Markdown",
|
|
434
|
+
_ => return None,
|
|
435
|
+
};
|
|
436
|
+
|
|
437
|
+
Some(language.to_string())
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/// Extract clean code from a response that might contain explanations
|
|
441
|
+
fn extract_code_from_response(response: &str) -> String {
|
|
442
|
+
// Try to find code blocks first
|
|
443
|
+
if let Some(start) = response.find("```")
|
|
444
|
+
&& let Some(end) = response[start + 3..].find("```") {
|
|
445
|
+
let code_block = &response[start + 3..start + 3 + end];
|
|
446
|
+
// Remove language identifier from first line if present
|
|
447
|
+
let lines: Vec<&str> = code_block.lines().collect();
|
|
448
|
+
if !lines.is_empty() {
|
|
449
|
+
let first_line = lines[0].trim();
|
|
450
|
+
if first_line
|
|
451
|
+
.chars()
|
|
452
|
+
.all(|c| c.is_alphabetic() || c == '+' || c == '#')
|
|
453
|
+
{
|
|
454
|
+
// First line is likely a language identifier
|
|
455
|
+
return lines[1..].join("\n").trim().to_string();
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
return code_block.trim().to_string();
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
// If no code blocks found, return the entire response
|
|
462
|
+
response.trim().to_string()
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
/// Save generated code to a file
|
|
466
|
+
fn save_code_to_file(code: &str, output_path: &str) -> Result<()> {
|
|
467
|
+
// Create parent directories if they don't exist
|
|
468
|
+
if let Some(parent) = Path::new(output_path).parent() {
|
|
469
|
+
fs::create_dir_all(parent).map_err(|e| anyhow!("Failed to create directory: {}", e))?;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
fs::write(output_path, code)
|
|
473
|
+
.map_err(|e| anyhow!("Failed to write file '{}': {}", output_path, e))?;
|
|
474
|
+
|
|
475
|
+
Ok(())
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
#[cfg(test)]
|
|
479
|
+
mod tests {
|
|
480
|
+
use super::*;
|
|
481
|
+
|
|
482
|
+
#[test]
|
|
483
|
+
fn test_detect_language_from_path() {
|
|
484
|
+
assert_eq!(
|
|
485
|
+
detect_language_from_path("main.rs"),
|
|
486
|
+
Some("Rust".to_string())
|
|
487
|
+
);
|
|
488
|
+
assert_eq!(
|
|
489
|
+
detect_language_from_path("script.py"),
|
|
490
|
+
Some("Python".to_string())
|
|
491
|
+
);
|
|
492
|
+
assert_eq!(
|
|
493
|
+
detect_language_from_path("app.js"),
|
|
494
|
+
Some("JavaScript".to_string())
|
|
495
|
+
);
|
|
496
|
+
assert_eq!(
|
|
497
|
+
detect_language_from_path("Component.tsx"),
|
|
498
|
+
Some("TypeScript".to_string())
|
|
499
|
+
);
|
|
500
|
+
assert_eq!(detect_language_from_path("unknown.xyz"), None);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
#[test]
|
|
504
|
+
fn test_extract_code_from_response() {
|
|
505
|
+
let response_with_blocks = "Here's the code:\n```rust\nfn main() {\n println!(\"Hello\");\n}\n```\nThat should work!";
|
|
506
|
+
let extracted = extract_code_from_response(response_with_blocks);
|
|
507
|
+
assert_eq!(extracted, "fn main() {\n println!(\"Hello\");\n}");
|
|
508
|
+
|
|
509
|
+
let response_without_blocks = "fn main() {\n println!(\"Hello\");\n}";
|
|
510
|
+
let extracted2 = extract_code_from_response(response_without_blocks);
|
|
511
|
+
assert_eq!(extracted2, "fn main() {\n println!(\"Hello\");\n}");
|
|
512
|
+
}
|
|
513
|
+
}
|