use std::path::Path;
use std::process::Command;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ProjectType {
Rust,
Python,
Go,
Template,
}
impl ProjectType {
pub fn detect(dir: &Path) -> Option<Self> {
if dir.join("Cargo.toml").exists() {
Some(Self::Rust)
} else if dir.join("go.mod").exists() {
Some(Self::Go)
} else if dir.join("setup.py").exists()
|| dir.join("pyproject.toml").exists()
|| dir.read_dir().ok()?.any(|e| {
e.ok()
.map(|e| e.path().extension().map(|e| e == "py").unwrap_or(false))
.unwrap_or(false)
})
{
Some(Self::Python)
} else {
None
}
}
pub fn source_extension(&self) -> &'static str {
match self {
Self::Rust => "rs",
Self::Python => "py",
Self::Go => "go",
Self::Template => "",
}
}
pub fn test_command(&self) -> Vec<&'static str> {
match self {
Self::Rust => vec!["cargo", "test"],
Self::Python => vec!["python3", "-m", "pytest", "-v"],
Self::Go => vec!["go", "test", "-v", "./..."],
Self::Template => vec!["cargo", "test"],
}
}
pub fn check_command(&self) -> Vec<&'static str> {
match self {
Self::Rust => vec!["cargo", "check"],
Self::Python => vec!["python3", "-m", "py_compile"],
Self::Go => vec!["go", "build", "./..."],
Self::Template => vec!["cargo", "check"],
}
}
}
pub fn scaffold_rust(name: &str, dir: &Path) -> std::io::Result<()> {
std::fs::create_dir_all(dir.join("src"))?;
let cargo_toml = format!(
r#"[package]
name = "{name}"
version = "0.1.0"
edition = "2021"
"#,
name = name.replace("-", "_")
);
std::fs::write(dir.join("Cargo.toml"), cargo_toml)?;
std::fs::write(dir.join("src/lib.rs"), "// Implement here\n")?;
let _ = Command::new("git")
.args(["init", "-q"])
.current_dir(dir)
.output();
let _ = Command::new("git")
.args(["add", "-A"])
.current_dir(dir)
.output();
let _ = Command::new("git")
.args(["commit", "-q", "-m", "init"])
.current_dir(dir)
.output();
Ok(())
}
pub fn scaffold_python(dir: &Path) -> std::io::Result<()> {
std::fs::create_dir_all(dir)?;
let _ = Command::new("git")
.args(["init", "-q"])
.current_dir(dir)
.output();
Ok(())
}
pub fn scaffold_go(module: &str, dir: &Path) -> std::io::Result<()> {
std::fs::create_dir_all(dir)?;
let go_mod = format!(
r#"module {module}
go 1.21
"#,
module = module
);
std::fs::write(dir.join("go.mod"), go_mod)?;
let _ = Command::new("git")
.args(["init", "-q"])
.current_dir(dir)
.output();
let _ = Command::new("git")
.args(["add", "-A"])
.current_dir(dir)
.output();
let _ = Command::new("git")
.args(["commit", "-q", "-m", "init"])
.current_dir(dir)
.output();
Ok(())
}
pub fn scaffold_template(template: &str, templates_dir: &Path, dir: &Path) -> std::io::Result<()> {
let src = templates_dir.join(template);
if !src.exists() {
return Err(std::io::Error::new(
std::io::ErrorKind::NotFound,
format!("Template not found: {}", src.display()),
));
}
copy_dir_all(&src, dir)?;
let _ = std::fs::remove_dir_all(dir.join("target"));
let _ = std::fs::remove_file(dir.join("Cargo.lock"));
let _ = std::fs::remove_dir_all(dir.join("node_modules"));
let _ = Command::new("git")
.args(["init", "-q"])
.current_dir(dir)
.output();
let _ = Command::new("git")
.args(["add", "-A"])
.current_dir(dir)
.output();
let _ = Command::new("git")
.args(["commit", "-q", "-m", "init"])
.current_dir(dir)
.output();
Ok(())
}
fn copy_dir_all(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> std::io::Result<()> {
std::fs::create_dir_all(&dst)?;
for entry in std::fs::read_dir(src)? {
let entry = entry?;
let ty = entry.file_type()?;
if ty.is_dir() {
copy_dir_all(entry.path(), dst.as_ref().join(entry.file_name()))?;
} else {
std::fs::copy(entry.path(), dst.as_ref().join(entry.file_name()))?;
}
}
Ok(())
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum ProjectStatus {
Green,
Partial,
Compiles,
Wrote,
Fail,
}
impl ProjectStatus {
pub fn from_results(compiles: bool, passed: usize, failed: usize, src_lines: usize) -> Self {
if compiles && passed > 0 && failed == 0 {
Self::Green
} else if compiles && passed > 0 {
Self::Partial
} else if compiles {
Self::Compiles
} else if src_lines > 2 {
Self::Wrote
} else {
Self::Fail
}
}
}
impl std::fmt::Display for ProjectStatus {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Green => write!(f, "GREEN"),
Self::Partial => write!(f, "PARTIAL"),
Self::Compiles => write!(f, "COMPILES"),
Self::Wrote => write!(f, "WROTE"),
Self::Fail => write!(f, "FAIL"),
}
}
}
#[derive(Debug, Clone)]
pub struct ProjectResult {
pub name: String,
pub status: ProjectStatus,
pub duration_secs: u64,
pub steps: usize,
pub src_lines: usize,
pub compiles: bool,
pub tests_passed: usize,
pub tests_failed: usize,
pub outcome_label: String,
}
#[cfg(test)]
#[path = "../../../tests/unit/bench_harness/long_running/project/project_test.rs"]
mod tests;