dampen_cli/commands/add/mod.rs
1//! Add command for scaffolding new UI windows.
2//!
3//! This module provides the `dampen add --ui <window_name>` command that generates
4//! UI window files (`.rs` and `.dampen`) based on templates.
5//!
6//! # Overview
7//!
8//! The `add` command scaffolds new UI windows for Dampen applications by:
9//! - Generating a Rust module with model, handlers, and AppState
10//! - Creating a corresponding `.dampen` XML file with basic UI layout
11//! - Validating window names and output paths
12//! - Preventing accidental file overwrites
13//!
14//! # Usage
15//!
16//! ## Basic Usage
17//!
18//! Create a new window in the default location (`src/ui/`):
19//!
20//! ```bash
21//! dampen add --ui settings
22//! ```
23//!
24//! This generates:
25//! - `src/ui/settings.rs` - Rust module with Model and handlers
26//! - `src/ui/settings.dampen` - XML UI definition
27//!
28//! ## Custom Output Directory
29//!
30//! Specify a custom output directory with `--path`:
31//!
32//! ```bash
33//! dampen add --ui order_form --path "src/ui/orders"
34//! ```
35//!
36//! This generates files in `src/ui/orders/`:
37//! - `src/ui/orders/order_form.rs`
38//! - `src/ui/orders/order_form.dampen`
39//!
40//! ## Window Name Conventions
41//!
42//! Window names are automatically converted to proper case:
43//! - Input: `UserProfile` → Files: `user_profile.rs`, `user_profile.dampen`
44//! - Input: `settings` → Files: `settings.rs`, `settings.dampen`
45//!
46//! # Generated Code Structure
47//!
48//! The generated Rust module includes:
49//! - `Model` struct with `#[derive(UiModel)]` for data binding
50//! - `create_app_state()` function that returns configured `AppState<Model>`
51//! - `create_handler_registry()` with sample event handlers
52//! - Auto-loading via `#[dampen_ui]` macro
53//!
54//! The generated XML includes:
55//! - Basic column layout with text and button widgets
56//! - Data binding example (`{message}`)
57//! - Event handler hookup (`on_click="on_action"`)
58//!
59//! # After Generation
60//!
61//! 1. Add the module to `src/ui/mod.rs`:
62//! ```rust,ignore
63//! pub mod settings;
64//! ```
65//!
66//! 2. Validate the XML:
67//! ```bash
68//! dampen check
69//! ```
70//!
71//! 3. Use in your application:
72//! ```rust,ignore
73//! use ui::settings;
74//! let state = settings::create_app_state();
75//! ```
76//!
77//! # Error Handling
78//!
79//! The command validates:
80//! - Project context (must be a Dampen project with `dampen-core` dependency)
81//! - Window name (must be valid Rust identifier, not a reserved keyword)
82//! - Output path (must be relative, within project bounds)
83//! - File conflicts (prevents overwriting existing files)
84//!
85//! # Examples
86//!
87//! ```bash
88//! # Create a settings window
89//! dampen add --ui settings
90//!
91//! # Create an admin dashboard in a subdirectory
92//! dampen add --ui dashboard --path "src/ui/admin"
93//!
94//! # Create an order form
95//! dampen add --ui OrderForm
96//! # → Generates: order_form.rs, order_form.dampen
97//! ```
98
99use clap::Args;
100
101pub mod errors;
102pub mod generation;
103pub mod integration;
104pub mod templates;
105pub mod validation;
106pub mod view_switching;
107
108// Export error types (Phase 2 complete)
109pub use errors::{GenerationError, PathError, ProjectError, ValidationError};
110
111// Export template types (Phase 2 complete)
112pub use templates::{TemplateKind, WindowNameVariants, WindowTemplate};
113
114// Export validation types (Phase 3-4 complete)
115pub use validation::{ProjectInfo, TargetPath, WindowName};
116
117// Export generation types (Phase 5)
118pub use generation::{GeneratedFiles, generate_window_files};
119
120// Types will be exported as they're implemented in later phases
121// pub use validation::{TargetPath};
122
123/// Arguments for the `dampen add` command.
124///
125/// # Examples
126///
127/// ```bash
128/// # Add a window in default location (src/ui/)
129/// dampen add --ui settings
130///
131/// # Add a window in custom location
132/// dampen add --ui admin_panel --path "src/ui/admin"
133/// ```
134///
135/// # Fields
136///
137/// - `ui`: Window name (converted to snake_case for filenames)
138/// - `path`: Custom output directory (relative to project root)
139#[derive(Debug, Args)]
140pub struct AddArgs {
141 /// Add a new UI window
142 ///
143 /// The window name will be converted to snake_case for filenames.
144 ///
145 /// Examples:
146 /// settings → settings.rs, settings.dampen
147 /// UserProfile → user_profile.rs, user_profile.dampen
148 /// admin-panel → admin_panel.rs, admin_panel.dampen
149 #[arg(long)]
150 pub ui: Option<String>,
151
152 /// Custom output directory path (relative to project root)
153 ///
154 /// If not provided, defaults to "src/ui/"
155 ///
156 /// Examples:
157 /// --path "src/ui/admin" → Files in src/ui/admin/
158 /// --path "ui/orders" → Files in ui/orders/
159 ///
160 /// Security:
161 /// - Must be relative (absolute paths rejected)
162 /// - Must be within project (cannot escape via ..)
163 #[arg(long)]
164 pub path: Option<String>,
165
166 /// Disable automatic integration (do not update mod.rs)
167 ///
168 /// By default, the command automatically adds `pub mod <window_name>;`
169 /// to the appropriate mod.rs file. Use this flag to disable automatic
170 /// integration and handle module registration manually.
171 ///
172 /// Example:
173 /// dampen add --ui settings --no-integrate
174 #[arg(long)]
175 pub no_integrate: bool,
176}
177
178/// Execute the add command.
179///
180/// This generates UI window files based on validated inputs.
181///
182/// # Process
183///
184/// 1. **Detect project**: Validates this is a Dampen project
185/// 2. **Validate name**: Checks window name is valid identifier
186/// 3. **Resolve path**: Determines output directory (default or custom)
187/// 4. **Generate files**: Creates .rs and .dampen files from templates
188/// 5. **Report success**: Shows file paths and next steps
189///
190/// # Errors
191///
192/// Returns `Err(String)` if:
193/// - Not in a Dampen project (no `dampen-core` in Cargo.toml)
194/// - Window name is invalid (empty, starts with number, reserved keyword)
195/// - Output path is invalid (absolute, escapes project)
196/// - Files already exist (prevents overwriting)
197/// - I/O errors occur during file creation
198///
199/// # Examples
200///
201/// ```no_run
202/// use dampen_cli::commands::add::{AddArgs, execute};
203///
204/// let args = AddArgs {
205/// ui: Some("settings".to_string()),
206/// path: None,
207/// no_integrate: false,
208/// };
209///
210/// match execute(&args) {
211/// Ok(()) => println!("Window created successfully"),
212/// Err(e) => eprintln!("Error: {}", e),
213/// }
214/// ```
215pub fn execute(args: &AddArgs) -> Result<(), String> {
216 // T073: Detect and validate project
217 let project_info = ProjectInfo::detect().map_err(|e| e.to_string())?;
218
219 if !project_info.is_dampen {
220 return Err(
221 "Error: Not a Dampen project (dampen-core dependency not found)\nhelp: Add dampen-core to your Cargo.toml, or run 'dampen new' to create a new project"
222 .to_string(),
223 );
224 }
225
226 // T074: Validate window name
227 let window_name_str = args
228 .ui
229 .as_ref()
230 .ok_or_else(|| "Error: Missing window name\nhelp: Use --ui <name>".to_string())?;
231
232 let window_name = WindowName::new(window_name_str).map_err(|e| e.to_string())?;
233
234 // T075: Resolve target path with validation (Phase 6)
235 let target_path =
236 TargetPath::resolve(&project_info.root, args.path.as_deref()).map_err(|e| e.to_string())?;
237
238 // T076: Generate files
239 let enable_integration = !args.no_integrate;
240 let generated = generate_window_files(&target_path, &window_name, enable_integration)
241 .map_err(|e| e.to_string())?;
242
243 // T077: Print success message
244 println!("{}", generated.success_message());
245
246 // T079: Return success
247 Ok(())
248}