#![allow(dead_code)] #![allow(clippy::uninlined_format_args)]
use std::fs;
use std::path::{Path, PathBuf};
use tempfile::TempDir;
pub trait ProjectBuilder {
fn build(self) -> (TempDir, PathBuf);
}
pub struct PythonProjectBuilder {
temp_dir: TempDir,
files: Vec<(PathBuf, String)>,
}
impl PythonProjectBuilder {
pub fn new() -> Self {
Self {
temp_dir: TempDir::new().unwrap(),
files: Vec::new(),
}
}
pub fn add_file<P: AsRef<Path>>(mut self, path: P, content: &str) -> Self {
self.files
.push((path.as_ref().to_path_buf(), content.to_string()));
self
}
pub fn with_main_imports(self, imports: &[&str]) -> Self {
let import_statements = imports
.iter()
.map(|imp| format!("import {}", imp))
.collect::<Vec<_>>()
.join("\n");
let content = format!(
"{}\n\ndef main():\n print('Python project')\n\nif __name__ == '__main__':\n main()",
import_statements
);
self.add_file("main.py", &content)
}
pub fn with_module(self, name: &str, functions: &[&str]) -> Self {
let func_defs = functions
.iter()
.map(|func| format!("def {}():\n pass", func))
.collect::<Vec<_>>()
.join("\n\n");
self.add_file(format!("{}.py", name), &func_defs)
}
}
impl ProjectBuilder for PythonProjectBuilder {
fn build(self) -> (TempDir, PathBuf) {
let root = self.temp_dir.path().to_path_buf();
fs::create_dir_all(root.join(".git")).unwrap();
for (path, content) in self.files {
let full_path = root.join(&path);
if let Some(parent) = full_path.parent() {
fs::create_dir_all(parent).unwrap();
}
fs::write(full_path, content).unwrap();
}
(self.temp_dir, root)
}
}
pub struct TypeScriptProjectBuilder {
temp_dir: TempDir,
files: Vec<(PathBuf, String)>,
}
impl TypeScriptProjectBuilder {
pub fn new() -> Self {
Self {
temp_dir: TempDir::new().unwrap(),
files: Vec::new(),
}
}
pub fn add_file<P: AsRef<Path>>(mut self, path: P, content: &str) -> Self {
self.files
.push((path.as_ref().to_path_buf(), content.to_string()));
self
}
pub fn with_index_imports(self, imports: &[(&str, &str)]) -> Self {
let import_statements = imports
.iter()
.map(|(what, from)| format!("import {{ {} }} from '{}';", what, from))
.collect::<Vec<_>>()
.join("\n");
let content = format!(
"{}\n\nfunction main(): void {{\n console.log('TypeScript project');\n}}\n\nmain();",
import_statements
);
self.add_file("src/index.ts", &content)
}
pub fn with_module(self, path: &str, exports: &[&str]) -> Self {
let export_defs = exports
.iter()
.map(|func| {
format!(
"export function {}(): void {{\n // Implementation\n}}",
func
)
})
.collect::<Vec<_>>()
.join("\n\n");
self.add_file(path, &export_defs)
}
pub fn with_interface(
self,
path: &str,
interface_name: &str,
properties: &[(&str, &str)],
) -> Self {
let props = properties
.iter()
.map(|(name, typ)| format!(" {}: {};", name, typ))
.collect::<Vec<_>>()
.join("\n");
let content = format!("export interface {} {{\n{}\n}}", interface_name, props);
self.add_file(path, &content)
}
}
impl ProjectBuilder for TypeScriptProjectBuilder {
fn build(self) -> (TempDir, PathBuf) {
let root = self.temp_dir.path().to_path_buf();
fs::create_dir_all(root.join(".git")).unwrap();
let package_json = r#"{
"name": "test-project",
"version": "1.0.0",
"type": "module"
}"#;
fs::write(root.join("package.json"), package_json).unwrap();
for (path, content) in self.files {
let full_path = root.join(&path);
if let Some(parent) = full_path.parent() {
fs::create_dir_all(parent).unwrap();
}
fs::write(full_path, content).unwrap();
}
(self.temp_dir, root)
}
}
pub struct RustProjectBuilder {
temp_dir: TempDir,
files: Vec<(PathBuf, String)>,
}
impl RustProjectBuilder {
pub fn new() -> Self {
Self {
temp_dir: TempDir::new().unwrap(),
files: Vec::new(),
}
}
pub fn add_file<P: AsRef<Path>>(mut self, path: P, content: &str) -> Self {
self.files
.push((path.as_ref().to_path_buf(), content.to_string()));
self
}
pub fn with_main_uses(self, uses: &[&str]) -> Self {
let use_statements = uses
.iter()
.map(|u| format!("use {};", u))
.collect::<Vec<_>>()
.join("\n");
let content = format!(
"{}\n\nfn main() {{\n println!(\"Rust project\");\n}}",
use_statements
);
self.add_file("src/main.rs", &content)
}
pub fn with_module(self, name: &str, functions: &[&str]) -> Self {
let func_defs = functions
.iter()
.map(|func| format!("pub fn {}() {{\n // Implementation\n}}", func))
.collect::<Vec<_>>()
.join("\n\n");
let content = format!("//! {} module\n\n{}", name, func_defs);
self.add_file(format!("src/{}.rs", name), &content)
}
pub fn with_struct(self, module: &str, struct_name: &str, fields: &[(&str, &str)]) -> Self {
let field_defs = fields
.iter()
.map(|(name, typ)| format!(" pub {}: {},", name, typ))
.collect::<Vec<_>>()
.join("\n");
let content = format!(
"#[derive(Debug, Clone)]\npub struct {} {{\n{}\n}}",
struct_name, field_defs
);
let existing = self
.files
.iter()
.find(|(path, _)| path == &PathBuf::from(format!("src/{}.rs", module)))
.map(|(_, content)| content.clone())
.unwrap_or_default();
let new_content = if existing.is_empty() {
content
} else {
format!("{}\n\n{}", existing, content)
};
let new_builder = Self {
temp_dir: self.temp_dir,
files: self
.files
.into_iter()
.filter(|(path, _)| path != &PathBuf::from(format!("src/{}.rs", module)))
.collect(),
};
new_builder.add_file(format!("src/{}.rs", module), &new_content)
}
}
impl ProjectBuilder for RustProjectBuilder {
fn build(self) -> (TempDir, PathBuf) {
let root = self.temp_dir.path().to_path_buf();
fs::create_dir_all(root.join(".git")).unwrap();
let cargo_toml = r#"[package]
name = "my_lib"
version = "0.1.0"
edition = "2021"
[dependencies]
"#;
fs::write(root.join("Cargo.toml"), cargo_toml).unwrap();
fs::create_dir_all(root.join("src")).unwrap();
for (path, content) in self.files {
let full_path = root.join(&path);
if let Some(parent) = full_path.parent() {
fs::create_dir_all(parent).unwrap();
}
fs::write(full_path, content).unwrap();
}
(self.temp_dir, root)
}
}
impl Default for PythonProjectBuilder {
fn default() -> Self {
Self::new()
}
}
impl Default for TypeScriptProjectBuilder {
fn default() -> Self {
Self::new()
}
}
impl Default for RustProjectBuilder {
fn default() -> Self {
Self::new()
}
}