Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions src/leetcode_api_runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use std::{
},
};

use colored::Colorize;
use colored::*;
use leetcoderustapi::{
problem_actions::Problem,
ProgrammingLanguage,
Expand All @@ -18,6 +18,7 @@ use crate::{
config::RuntimeConfigSetup,
local_config::LocalConfig,
readme_parser::LeetcodeReadmeParser,
result_formatter::format_test_result,
test_generator::TestGenerator,
utils::*,
};
Expand Down Expand Up @@ -162,12 +163,15 @@ impl LeetcodeApiRunner {
&self, id: u32, path_to_file: &String,
) -> io::Result<String> {
let problem_info = self.api.set_problem_by_id(id).await?;
let name = problem_info.description()?.name;
let file_content = std::fs::read_to_string(path_to_file)
.expect("Unable to read the file");
let language = get_language_from_extension(path_to_file);

let test_res = problem_info.send_test(language, &file_content).await?;
Ok(format!("Test response for problem {id}: \n{test_res:#?}"))
let _ = run_local_check(path_to_file, &language).await?;
let processed_code = preprocess_code(&file_content, &language);
let test_res =
problem_info.send_test(language, &processed_code).await?;
Ok(format_test_result(id, &name, &test_res))
}

pub async fn submit_response(
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ pub mod config;
pub mod leetcode_api_runner;
pub mod local_config;
pub mod readme_parser;
pub mod result_formatter;
pub mod test_generator;
pub mod utils;

Expand Down
151 changes: 151 additions & 0 deletions src/result_formatter.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
use colored::Colorize;
use leetcoderustapi::resources::test_send::TestExecutionResult;

pub fn format_test_result(
id: u32, name: &str, result: &TestExecutionResult,
) -> String {
let mut output = String::new();

output.push_str(&format!("🧪 Test Results for Problem {id}: {name}\n"));
output.push_str(&"=".repeat(50));
output.push('\n');
output.push_str(&format_status_message(result.status_msg.as_deref()));
output.push('\n');

// Language
if let Some(ref lang) = result.pretty_lang {
output.push_str(&format!("🔧 Language: {}\n", lang.cyan()));
}

// // Execution success
// if let Some(run_success) = result.run_success {
// let success_text = if run_success { "Yes".green() } else { "No".red()
// }; output.push_str(&format!("> Execution Success: {}\n",
// success_text)); }
Copy link

Copilot AI Aug 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This large block of commented code (lines 20-24) should be removed if it's no longer needed, or converted to a TODO comment if it represents planned functionality.

Suggested change
// success_text)); }

Copilot uses AI. Check for mistakes.

// Test case results
if let (Some(correct), Some(total)) =
(result.total_correct, result.total_testcases)
{
let ratio_color = if correct == total {
|s: String| s.green()
} else if correct > 0 {
|s: String| s.yellow()
} else {
|s: String| s.red()
};
output.push_str(&format!(
"📊 Test Cases: {}\n",
ratio_color(format!("{correct}/{total}"))
));
}

// Runtime and memory (only if successful)
if result.run_success == Some(true) {
if let Some(ref runtime) = result.status_runtime {
if runtime != "N/A" {
output.push_str(&format!("⏱️ Runtime: {}\n", runtime.blue()));
}
}

if let Some(ref memory) = result.status_memory {
if memory != "N/A" {
output.push_str(&format!("💾 Memory: {}\n", memory.blue()));
}
}

// Percentiles if available
if let Some(Some(runtime_perc)) = result.runtime_percentile {
output.push_str(&format!(
"📈 Runtime Percentile: {runtime_perc:.1}%\n"
));
}

if let Some(Some(memory_perc)) = result.memory_percentile {
output.push_str(&format!(
"📈 Memory Percentile: {memory_perc:.1}%\n"
));
}
}

// Compilation errors
if let Some(ref compile_error) = result.compile_error {
if !compile_error.is_empty() {
output.push_str(&format!(
"\n🔴 {}\n",
"Compilation Error:".red().bold()
));
output.push_str(&format!("{}\n", compile_error.red()));
}
}

// Detailed compilation errors
if let Some(ref full_error) = result.full_compile_error {
if !full_error.is_empty() && result.compile_error.is_none() {
output.push_str(&format!(
"\n📋 {}\n",
"Detailed Error:".red().bold()
));
output.push_str(&format!("{full_error}\n"));
}
}

// Wrong answer details
if let Some(ref code_output) = result.code_output {
if !code_output.is_empty() {
output.push_str(&format!("\n❌ {}\n", "Your Output:".red().bold()));
for (i, out) in code_output.iter().enumerate() {
output.push_str(&format!("Test {}: {}\n", i + 1, out));
}
}
}

if let Some(ref expected_output) = result.expected_code_output {
if !expected_output.is_empty() {
output.push_str(&format!(
"\n✅ {}\n",
"Expected Output:".green().bold()
));
for (i, out) in expected_output.iter().enumerate() {
output.push_str(&format!("Test {}: {}\n", i + 1, out));
}
}
}

// Standard output (if any)
if let Some(ref std_output) = result.std_output_list {
if !std_output.is_empty()
&& std_output.iter().any(|s| !s.trim().is_empty())
{
output.push_str(&format!(
"\n📤 {}\n",
"Standard Output:".blue().bold()
));
for (i, out) in std_output.iter().enumerate() {
if !out.trim().is_empty() {
output.push_str(&format!("Test {}: {}\n", i + 1, out));
}
}
}
}

output.push('\n');
output
}

// Status with icon
fn format_status_message(status: Option<&str>) -> String {
match status {
Some("Accepted") => "✅ Accepted".green().to_string(),
Some("Wrong Answer") => "❌ Wrong Answer".red().to_string(),
Some("Compile Error") => "🔴 Compile Error".red().to_string(),
Some("Runtime Error") => "⚠️ Runtime Error".red().to_string(),
Some("Time Limit Exceeded") => {
"⏰ Time Limit Exceeded".yellow().to_string()
},
Some("Memory Limit Exceeded") => {
"💾 Memory Limit Exceeded".yellow().to_string()
},
_ => "Unknown Status".yellow().to_string(),
}
}
145 changes: 121 additions & 24 deletions src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,30 @@ pub fn get_language_from_extension(
}
}

pub fn get_extension_from_language(
lang: &leetcoderustapi::ProgrammingLanguage,
) -> String {
match lang {
leetcoderustapi::ProgrammingLanguage::CPP => "cpp".to_string(),
leetcoderustapi::ProgrammingLanguage::Java => "java".to_string(),
leetcoderustapi::ProgrammingLanguage::Python => "py".to_string(),
leetcoderustapi::ProgrammingLanguage::Python3 => "py".to_string(),
leetcoderustapi::ProgrammingLanguage::C => "c".to_string(),
leetcoderustapi::ProgrammingLanguage::CSharp => "cs".to_string(),
leetcoderustapi::ProgrammingLanguage::JavaScript => "js".to_string(),
leetcoderustapi::ProgrammingLanguage::TypeScript => "ts".to_string(),
leetcoderustapi::ProgrammingLanguage::Ruby => "rb".to_string(),
leetcoderustapi::ProgrammingLanguage::Swift => "swift".to_string(),
leetcoderustapi::ProgrammingLanguage::Go => "go".to_string(),
leetcoderustapi::ProgrammingLanguage::Bash => "sh".to_string(),
leetcoderustapi::ProgrammingLanguage::Scala => "scala".to_string(),
leetcoderustapi::ProgrammingLanguage::Kotlin => "kt".to_string(),
leetcoderustapi::ProgrammingLanguage::Rust => "rs".to_string(),
leetcoderustapi::ProgrammingLanguage::PHP => "php".to_string(),
_ => panic!("Unsupported language: {lang:?}"),
}
}

pub fn spin_the_spinner(message: &str) -> spinners::Spinner {
spinners::Spinner::new(spinners::Spinners::Dots12, message.to_string())
}
Expand Down Expand Up @@ -174,30 +198,6 @@ pub fn prompt_for_language(
}
}

pub fn get_extension_from_language(
lang: &leetcoderustapi::ProgrammingLanguage,
) -> String {
match lang {
leetcoderustapi::ProgrammingLanguage::CPP => "cpp".to_string(),
leetcoderustapi::ProgrammingLanguage::Java => "java".to_string(),
leetcoderustapi::ProgrammingLanguage::Python => "py".to_string(),
leetcoderustapi::ProgrammingLanguage::Python3 => "py".to_string(),
leetcoderustapi::ProgrammingLanguage::C => "c".to_string(),
leetcoderustapi::ProgrammingLanguage::CSharp => "cs".to_string(),
leetcoderustapi::ProgrammingLanguage::JavaScript => "js".to_string(),
leetcoderustapi::ProgrammingLanguage::TypeScript => "ts".to_string(),
leetcoderustapi::ProgrammingLanguage::Ruby => "rb".to_string(),
leetcoderustapi::ProgrammingLanguage::Swift => "swift".to_string(),
leetcoderustapi::ProgrammingLanguage::Go => "go".to_string(),
leetcoderustapi::ProgrammingLanguage::Bash => "sh".to_string(),
leetcoderustapi::ProgrammingLanguage::Scala => "scala".to_string(),
leetcoderustapi::ProgrammingLanguage::Kotlin => "kt".to_string(),
leetcoderustapi::ProgrammingLanguage::Rust => "rs".to_string(),
leetcoderustapi::ProgrammingLanguage::PHP => "php".to_string(),
_ => panic!("Unsupported language: {lang:?}"),
}
}

pub fn prefix_code(file_content: &str, lang: &ProgrammingLanguage) -> String {
let prefix = match lang {
ProgrammingLanguage::Rust => "pub struct Solution;\n\n".to_string(),
Expand Down Expand Up @@ -241,3 +241,100 @@ pub fn difficulty_color(difficulty: &str) -> colored::ColoredString {
_ => "Unknown".normal(),
}
}

/// Preprocesses file content before sending to LeetCode by removing local
/// compilation helpers
pub fn preprocess_code(
content: &str, language: &ProgrammingLanguage,
) -> String {
match language {
ProgrammingLanguage::Rust => preprocess_rust_content(content),
// For other languages, return as-is for now
_ => content.to_string(),
}
}

/// Removes pub struct Solution; from the top of the file
fn preprocess_rust_content(content: &str) -> String {
let n = delete_line_content(content, "pub struct Solution;");
remove_main(&n)
}
fn remove_main(content: &str) -> String {
let mut c = vec![];

for line in content.lines() {
if line.contains("fn main() {") {
break;
}
c.push(line);
}
c.join("\n")
}

fn delete_line_content(content: &str, target: &str) -> String {
content
.lines()
.filter(|line| line.trim() != target)
.collect::<Vec<_>>()
.join("\n")
}

/// Find the nearest Cargo project root (directory containing Cargo.toml)
/// starting from `start_dir` and walking up.
fn find_manifest_dir(start_dir: &Path) -> Option<PathBuf> {
for dir in start_dir.ancestors() {
let candidate = dir.join("Cargo.toml");
if candidate.is_file() {
return Some(dir.to_path_buf());
}
}
None
}

/// Runs local compilation check before sending to LeetCode
pub async fn run_local_check(
path_to_file: &str, language: &ProgrammingLanguage,
) -> io::Result<String> {
use std::process::Command;

match language {
ProgrammingLanguage::Rust => {
let file_path = Path::new(path_to_file);

// If within a Cargo project, run `cargo check` at the project root
if let Some(parent) = file_path.parent() {
if let Some(manifest_dir) = find_manifest_dir(parent) {
let output = Command::new("cargo")
.args(["check", "--quiet"])
.current_dir(&manifest_dir)
.output()?;

if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
return Ok(format!("❌ Local check failed:\n{stderr}"));
}

return Ok("✅ Local compilation passed!".to_string());
}
}

// Fallback: compile the single file directly with rustc
let output = Command::new("rustc")
.args([
"--edition=2021",
"--emit=metadata",
"--crate-type=bin",
path_to_file,
])
.output()?;

if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
return Ok(format!("❌ Compilation failed:\n{stderr}"));
}

Ok("✅ Local compilation passed!".to_string())
},
_ => Ok(format!("⚠️ Local check not implemented for {language:?}",)),
}
}
Loading