pub mod common;
pub mod nextjs;
pub mod rust;
use crate::utils::error::{Error, Result};
use std::path::{Path, PathBuf};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ProjectType {
RustWeb,
RustCli,
RustLib,
NextJs,
Nuxt,
}
impl std::fmt::Display for ProjectType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ProjectType::RustWeb => write!(f, "rust-web"),
ProjectType::RustCli => write!(f, "rust-cli"),
ProjectType::RustLib => write!(f, "rust-lib"),
ProjectType::NextJs => write!(f, "nextjs"),
ProjectType::Nuxt => write!(f, "nuxt"),
}
}
}
impl std::str::FromStr for ProjectType {
type Err = Error;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"rust-web" => Ok(ProjectType::RustWeb),
"rust-cli" => Ok(ProjectType::RustCli),
"rust-lib" => Ok(ProjectType::RustLib),
"nextjs" => Ok(ProjectType::NextJs),
"nuxt" => Ok(ProjectType::Nuxt),
_ => Err(Error::new(&format!("Unsupported project type: {}", s))),
}
}
}
#[derive(Debug, Clone)]
pub struct ProjectConfig {
pub name: String,
pub project_type: ProjectType,
pub framework: Option<String>,
pub path: PathBuf,
}
#[derive(Debug, Clone, Default)]
pub struct ProjectStructure {
pub files: Vec<(String, String)>,
pub directories: Vec<String>,
}
pub trait ProjectGenerator: Send + Sync {
fn generate(&self, config: &ProjectConfig) -> Result<ProjectStructure>;
fn supported_types(&self) -> Vec<ProjectType>;
}
pub struct GeneratorFactory;
impl GeneratorFactory {
pub fn create(project_type: &ProjectType) -> Result<Box<dyn ProjectGenerator>> {
match project_type {
ProjectType::RustWeb | ProjectType::RustCli | ProjectType::RustLib => {
Ok(Box::new(rust::RustProjectGenerator::new()))
}
ProjectType::NextJs | ProjectType::Nuxt => Ok(Box::new(nextjs::NextJsGenerator::new())),
}
}
}
pub struct FileSystemWriter;
impl Default for FileSystemWriter {
fn default() -> Self {
Self
}
}
impl FileSystemWriter {
pub fn new() -> Self {
Self
}
pub fn write_file(&self, path: &Path, content: &str) -> Result<()> {
std::fs::write(path, content).map_err(|e| {
Error::with_source(
&format!("Failed to write file {}", path.display()),
Box::new(e),
)
})
}
pub fn create_directory(&self, path: &Path) -> Result<()> {
std::fs::create_dir_all(path).map_err(|e| {
Error::with_source(
&format!("Failed to create directory {}", path.display()),
Box::new(e),
)
})
}
}
pub struct GitInitializer;
impl Default for GitInitializer {
fn default() -> Self {
Self
}
}
impl GitInitializer {
pub fn new() -> Self {
Self
}
pub fn initialize(&self, path: &Path) -> Result<()> {
use crate::security::command::SafeCommand;
let output = SafeCommand::new("git")?
.arg("init")?
.current_dir(path)?
.execute()
.map_err(|e| Error::with_source("Failed to run git init", Box::new(e)))?;
if !output.status.success() {
return Err(Error::new(&format!(
"git init failed: {}",
String::from_utf8_lossy(&output.stderr)
)));
}
Ok(())
}
}
pub struct DependencyInstaller;
impl Default for DependencyInstaller {
fn default() -> Self {
Self
}
}
impl DependencyInstaller {
pub fn new() -> Self {
Self
}
pub fn install(&self, path: &Path, project_type: &ProjectType) -> Result<()> {
match project_type {
ProjectType::RustWeb | ProjectType::RustCli | ProjectType::RustLib => {
self.install_cargo_deps(path)
}
ProjectType::NextJs | ProjectType::Nuxt => self.install_npm_deps(path),
}
}
fn install_cargo_deps(&self, path: &Path) -> Result<()> {
use crate::security::command::SafeCommand;
crate::alert_info!("Installing Cargo dependencies...");
let output = SafeCommand::new("cargo")?
.arg("fetch")?
.current_dir(path)?
.execute()
.map_err(|e| Error::with_source("Failed to run cargo fetch", Box::new(e)))?;
if !output.status.success() {
let error_msg = format!(
"cargo fetch failed: {}",
String::from_utf8_lossy(&output.stderr)
);
crate::alert_warning!(&error_msg);
}
Ok(())
}
fn install_npm_deps(&self, path: &Path) -> Result<()> {
use crate::security::command::SafeCommand;
crate::alert_info!("Installing npm dependencies...");
let output = SafeCommand::new("npm")?
.arg("install")?
.current_dir(path)?
.execute()?;
if !output.status.success() {
return Err(Error::new(&format!(
"npm install failed: {}",
String::from_utf8_lossy(&output.stderr)
)));
}
Ok(())
}
}
pub async fn create_new_project(config: &ProjectConfig) -> Result<()> {
let project_path = config.path.join(&config.name);
if project_path.exists() {
return Err(Error::new(&format!(
"Project directory '{}' already exists",
config.name
)));
}
let generator = GeneratorFactory::create(&config.project_type)?;
let structure = generator.generate(config)?;
let fs_writer = FileSystemWriter::new();
fs_writer.create_directory(&project_path)?;
for dir in &structure.directories {
let dir_path = project_path.join(dir);
fs_writer.create_directory(&dir_path)?;
}
for (file_path, content) in &structure.files {
let full_path = project_path.join(file_path);
if let Some(parent) = full_path.parent() {
fs_writer.create_directory(parent)?;
}
fs_writer.write_file(&full_path, content)?;
}
let git = GitInitializer::new();
git.initialize(&project_path)?;
let deps = DependencyInstaller::new();
deps.install(&project_path, &config.project_type)?;
crate::alert_success!(&format!("Successfully created project: {}", config.name));
crate::alert_info!(&format!(" Type: {}", config.project_type));
crate::alert_info!(&format!(" Path: {}", project_path.display()));
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_project_type_from_str() {
assert_eq!(
"rust-web".parse::<ProjectType>().unwrap(),
ProjectType::RustWeb
);
assert_eq!(
"nextjs".parse::<ProjectType>().unwrap(),
ProjectType::NextJs
);
}
#[test]
fn test_project_type_display() {
assert_eq!(ProjectType::RustWeb.to_string(), "rust-web");
assert_eq!(ProjectType::NextJs.to_string(), "nextjs");
}
}