use colored::*;
use std::fs;
use std::path::{Path, PathBuf};
use std::process::Command;
use super::output;
fn find_project_root() -> Option<PathBuf> {
let mut current = std::env::current_dir().ok()?;
loop {
if current.join("Cargo.toml").exists() {
return Some(current);
}
if !current.pop() {
return None;
}
}
}
pub fn run_file(file: &str, extra_deps: Option<&str>) -> Result<(), Box<dyn std::error::Error>> {
let file_path = Path::new(file);
if !file_path.exists() {
return Err(format!("File not found: {}", file).into());
}
if !file_path.extension().map_or(false, |ext| ext == "rs") {
return Err("File must be a .rs file".into());
}
output::header("Oxidite Runner v2.3.2");
output::debug(&format!("Executing file: {}", file_path.display()));
if let Some(project_root) = find_project_root() {
output::info("Running in project mode");
output::debug(&format!("Project root: {}", project_root.display()));
run_in_project(&project_root, file_path, extra_deps)
} else {
output::info("Running in standalone mode");
run_standalone(file_path, extra_deps)
}
}
fn run_in_project(
project_root: &Path,
file_path: &Path,
extra_deps: Option<&str>,
) -> Result<(), Box<dyn std::error::Error>> {
let src_bin = project_root.join("src").join("bin");
fs::create_dir_all(&src_bin)?;
let bin_name = file_path
.file_stem()
.and_then(|s| s.to_str())
.ok_or("Could not determine binary name")?;
let target_path = src_bin.join(format!("{}.rs", bin_name));
let should_copy = if target_path.exists() {
fs::canonicalize(file_path)? != fs::canonicalize(&target_path)?
} else {
true
};
if should_copy {
fs::copy(file_path, &target_path)?;
output::info(&format!("Copied {} -> {}", file_path.display(), target_path.display()));
}
if let Some(deps) = extra_deps {
output::debug(&format!("Adding dependencies: {}", deps));
add_dependencies(project_root, deps)?;
}
output::step(&format!("Running {} in project context", bin_name.bold()));
let mut cmd = Command::new("cargo");
cmd.arg("run").arg("--bin").arg(bin_name);
cmd.stdout(std::process::Stdio::inherit())
.stderr(std::process::Stdio::inherit())
.stdin(std::process::Stdio::inherit())
.current_dir(project_root);
let status = cmd.status()?;
if !status.success() {
return Err(format!("Script exited with status: {}", status).into());
}
output::success("Completed successfully");
Ok(())
}
fn run_standalone(
file_path: &Path,
extra_deps: Option<&str>,
) -> Result<(), Box<dyn std::error::Error>> {
let temp_dir = std::env::temp_dir().join("oxidite_run_temp");
if temp_dir.exists() {
fs::remove_dir_all(&temp_dir)?;
}
fs::create_dir_all(temp_dir.join("src"))?;
fs::copy(file_path, temp_dir.join("src").join("main.rs"))?;
let mut deps = vec![
("oxidite", "2.3.2"),
("oxidite-db", "2.3.2"),
("oxidite-config", "2.3.2"),
("oxidite-core", "2.3.2"),
("tokio", "1"),
];
if let Some(extra) = extra_deps {
for dep in extra.split(',') {
let dep = dep.trim();
if !dep.is_empty() {
deps.push((dep, "*"));
}
}
}
let cargo_toml = format!(
r#"[package]
name = "oxidite-script"
version = "0.1.0"
edition = "2021"
[dependencies]
{}
"#,
deps.iter()
.map(|(name, version)| format!("{} = \"{}\"", name, version))
.collect::<Vec<_>>()
.join("\n")
);
fs::write(temp_dir.join("Cargo.toml"), cargo_toml)?;
output::info(&format!("Running {} (standalone mode)", file_path.display()));
output::debug(&format!("Using temp project at {}", temp_dir.display()));
let status = Command::new("cargo")
.arg("run")
.arg("--quiet")
.stdout(std::process::Stdio::inherit())
.stderr(std::process::Stdio::inherit())
.stdin(std::process::Stdio::inherit())
.current_dir(&temp_dir)
.status()?;
let _ = fs::remove_dir_all(&temp_dir);
if !status.success() {
return Err(format!("Script exited with status: {}", status).into());
}
output::success("Completed successfully");
Ok(())
}
fn add_dependencies(project_root: &Path, deps: &str) -> Result<(), Box<dyn std::error::Error>> {
let cargo_toml_path = project_root.join("Cargo.toml");
let mut cargo_toml = fs::read_to_string(&cargo_toml_path)?;
let mut needs_update = false;
for dep in deps.split(',') {
let dep = dep.trim();
if dep.is_empty() {
continue;
}
if !cargo_toml.contains(&format!("{} = ", dep)) && !cargo_toml.contains(&format!("{}=", dep)) {
if cargo_toml.contains("[dependencies]") {
let lines: Vec<&str> = cargo_toml.lines().collect();
let mut new_lines: Vec<String> = Vec::new();
let mut in_deps = false;
let mut inserted = false;
let dep_line = format!("{} = \"*\"", dep);
for line in &lines {
if line.trim().starts_with('[') && in_deps && !inserted {
new_lines.push(dep_line.clone());
inserted = true;
in_deps = false;
}
if line.trim() == "[dependencies]" {
in_deps = true;
}
new_lines.push(line.to_string());
}
if !inserted {
new_lines.push(dep_line);
}
cargo_toml = new_lines.join("\n");
needs_update = true;
}
}
}
if needs_update {
fs::write(cargo_toml_path, cargo_toml)?;
output::info(&format!("Added dependencies: {}", deps));
}
Ok(())
}