From 479876114fd47e89c9e2bb9fa138f79ad356a247 Mon Sep 17 00:00:00 2001 From: Da7-Tech <286182457+Da7-Tech@users.noreply.github.com> Date: Wed, 20 May 2026 05:00:25 +0300 Subject: [PATCH] Implement cx new project templates --- wezterm/src/cli/new.rs | 971 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 956 insertions(+), 15 deletions(-) diff --git a/wezterm/src/cli/new.rs b/wezterm/src/cli/new.rs index 30242fd8e..2148a221d 100644 --- a/wezterm/src/cli/new.rs +++ b/wezterm/src/cli/new.rs @@ -3,12 +3,17 @@ Copyright (c) 2026 AI Venture Holdings LLC Licensed under the Business Source License 1.1 You may not use this file except in compliance with the License. */ -//! CX Terminal: Create new projects from templates -//! -//! This module provides the `new` command for creating new projects -//! from predefined templates. Templates include common project types -//! like Rust, Python, Node.js, and more. +// CX Terminal: Create new projects from templates. +// +// This module provides the `new` command for creating new projects +// from predefined templates. Templates include common project types +// like Rust, Python, Node.js, and more. +use std::fs; +use std::path::{Component, Path, PathBuf}; +use std::process::Command; + +use anyhow::{bail, Context, Result}; use clap::Parser; /// Command to create a new project from a template. @@ -16,8 +21,9 @@ use clap::Parser; /// # Examples /// /// ```bash -/// cx-terminal new rust --name my-project -/// cx-terminal new python --dir /path/to/projects +/// cx-terminal new rust my-project +/// cx-terminal new python --name my-project --dir /path/to/projects +/// cx-terminal new --list /// ``` #[derive(Debug, Parser, Clone)] pub struct NewCommand { @@ -25,6 +31,10 @@ pub struct NewCommand { #[arg(default_value = "default")] pub template: String, + /// The name of the new project + #[arg(value_name = "NAME")] + pub project_name: Option, + /// The name of the new project #[arg(short, long)] pub name: Option, @@ -32,23 +42,954 @@ pub struct NewCommand { /// The directory to create the project in #[arg(short, long)] pub dir: Option, + + /// List available templates + #[arg(long)] + pub list: bool, } impl NewCommand { /// Execute the new command to create a project from template. /// - /// Currently a stub implementation that prints a message. - /// Future versions will scaffold actual project templates. - /// /// # Returns /// /// Returns `Ok(())` on success, or an error if project creation fails. - pub fn run(&self) -> anyhow::Result<()> { - eprintln!( - "CX Terminal: 'new' command is not yet implemented. Template: {}", - self.template + pub fn run(&self) -> Result<()> { + if self.list || (self.template == "default" && self.effective_name()?.is_none()) { + print_available_templates(); + return Ok(()); + } + + let requested_template = self.template.to_lowercase(); + let known_template = find_template(&requested_template); + let (template, project_name) = match (known_template, self.effective_name()?) { + (Some(template), Some(project_name)) => (template, project_name), + (Some(_), None) => { + bail!( + "Project name is required. Try: cx new {} my-project", + self.template + ) + } + (None, None) if self.project_name.is_none() && self.name.is_none() => { + let inferred_template = infer_default_template(); + let template = find_template(inferred_template) + .context("internal error: inferred template is not registered")?; + (template, self.template.clone()) + } + (None, Some(project_name)) if requested_template == "default" => { + let inferred_template = infer_default_template(); + let template = find_template(inferred_template) + .context("internal error: inferred template is not registered")?; + (template, project_name) + } + (None, _) => { + bail!( + "Unknown template '{}'. Run 'cx new --list' to see available templates.", + self.template + ) + } + }; + + validate_project_name(&project_name)?; + let context = TemplateContext::new(&project_name); + let base_dir = match &self.dir { + Some(dir) => PathBuf::from(dir), + None => std::env::current_dir().context("failed to read current directory")?, + }; + let target_dir = base_dir.join(&project_name); + + ensure_target_directory_is_ready(&target_dir)?; + write_template(&target_dir, &template, &context)?; + + if template.create_python_venv { + create_python_venv(&target_dir)?; + } + + println!( + "Created '{}' project '{}' at {}", + template.name, + project_name, + target_dir.display() ); - eprintln!("This feature will create new projects from templates."); + println!("Next steps:"); + for step in template.next_steps.iter() { + println!(" {}", render(step, &context)); + } + Ok(()) } + + fn effective_name(&self) -> Result> { + match (&self.project_name, &self.name) { + (Some(_), Some(_)) => { + bail!("Project name was provided twice. Use either positional NAME or --name.") + } + (Some(name), None) | (None, Some(name)) => Ok(Some(name.clone())), + (None, None) => Ok(None), + } + } +} + +#[derive(Debug, Clone)] +struct Template { + name: &'static str, + description: &'static str, + files: Vec, + next_steps: Vec<&'static str>, + create_python_venv: bool, +} + +#[derive(Debug, Clone)] +struct TemplateFile { + path: &'static str, + contents: &'static str, +} + +#[derive(Debug, Clone)] +struct TemplateContext { + project_name: String, + package_name: String, + rust_crate: String, + python_module: String, + go_module: String, +} + +impl TemplateContext { + fn new(project_name: &str) -> Self { + let package_name = sanitize_package_name(project_name); + let identifier = sanitize_identifier(project_name); + + Self { + project_name: project_name.to_string(), + package_name, + rust_crate: identifier.clone(), + python_module: identifier.clone(), + go_module: format!("example.com/{}", identifier), + } + } +} + +fn print_available_templates() { + println!("Available templates:"); + for template in templates() { + println!(" {:<8} {}", template.name, template.description); + } + println!(); + println!("Usage:"); + println!(" cx new