kasl/commands/template.rs
1//! Task template management command.
2//!
3//! Provides comprehensive template management functionality for kasl, enabling users to create, edit, delete, and search reusable task templates.
4//!
5//! ## Features
6//!
7//! - **Template CRUD**: Create, read, update, and delete operations
8//! - **Search Functionality**: Find templates by name or content
9//! - **Interactive Management**: User-friendly interfaces for all operations
10//! - **Validation**: Ensures template data integrity and uniqueness
11//! - **Integration**: Seamless integration with task creation workflows
12//!
13//! ## Usage
14//!
15//! ```bash
16//! # List all templates
17//! kasl template list
18//!
19//! # Create new template
20//! kasl template create --name "bug-fix"
21//!
22//! # Search templates
23//! kasl template search "development"
24//!
25//! # Delete template
26//! kasl template delete "old-template"
27//! ```
28
29use crate::{
30 db::templates::{TaskTemplate, Templates},
31 libs::{messages::Message, view::View},
32 msg_error, msg_info, msg_print, msg_success,
33};
34use anyhow::Result;
35use clap::{Args, Subcommand};
36use dialoguer::{Confirm, Input, Select, theme::ColorfulTheme};
37
38/// Command-line arguments for template management operations.
39///
40/// The template command uses subcommands to organize different template
41/// management operations, providing a clean and intuitive interface for
42/// users to manage their template library.
43#[derive(Debug, Args)]
44pub struct TemplateArgs {
45 #[command(subcommand)]
46 command: Option<TemplateCommand>,
47}
48
49/// Available template management operations.
50///
51/// Each subcommand provides specific functionality for template lifecycle
52/// management, from creation through deletion, with support for both
53/// direct command-line usage and interactive operation.
54#[derive(Debug, Subcommand)]
55enum TemplateCommand {
56 /// Create a new task template
57 ///
58 /// Creates a new reusable template with specified or interactive values.
59 /// Templates provide default values for task creation, streamlining
60 /// workflows for frequently created task types.
61 Create {
62 /// Unique name identifier for the template
63 ///
64 /// Must be unique across all templates and should be descriptive
65 /// enough to easily identify the template's purpose. Used for
66 /// referencing the template in task creation commands.
67 #[arg(short, long)]
68 name: Option<String>,
69 },
70
71 /// List all available templates
72 ///
73 /// Displays a formatted table of all existing templates with their
74 /// names, task names, comments, and default completion values.
75 /// Useful for reviewing available templates and their configurations.
76 List,
77
78 /// Edit an existing template
79 ///
80 /// Modifies an existing template's properties including task name,
81 /// comment, and completion status. Provides interactive interface
82 /// for template selection if name is not specified.
83 Edit {
84 /// Name of the template to edit
85 ///
86 /// If not provided, an interactive selection interface will be
87 /// presented with all available templates.
88 name: Option<String>,
89 },
90
91 /// Delete a template
92 ///
93 /// Permanently removes a template from the system. Includes confirmation
94 /// prompt to prevent accidental deletion. Template deletion does not
95 /// affect tasks that were previously created from the template.
96 Delete {
97 /// Name of the template to delete
98 ///
99 /// If not provided, an interactive selection interface will be
100 /// presented with all available templates.
101 name: Option<String>,
102 },
103
104 /// Search templates by name or content
105 ///
106 /// Performs a text search across template names and task names,
107 /// returning all matching templates. Useful for finding templates
108 /// in large template libraries.
109 Search {
110 /// Search query string
111 ///
112 /// Searches both template names and task names for matches.
113 /// Case-insensitive partial matching is supported.
114 query: String,
115 },
116}
117
118/// Executes template management operations based on the specified subcommand.
119///
120/// This function serves as the main dispatcher for template operations,
121/// routing to appropriate handlers based on user input. When no subcommand
122/// is provided, it enters interactive mode for operation selection.
123///
124/// ## Operation Routing
125///
126/// - **Create**: Template creation with validation and uniqueness checking
127/// - **List**: Formatted display of all available templates
128/// - **Edit**: Interactive or direct template modification
129/// - **Delete**: Safe template removal with confirmation
130/// - **Search**: Text-based template discovery
131/// - **Interactive**: Menu-driven operation selection when no subcommand given
132///
133/// # Arguments
134///
135/// * `args` - Parsed command-line arguments containing operation specification
136///
137/// # Returns
138///
139/// Returns `Ok(())` on successful operation completion, or an error if
140/// the requested operation fails due to validation, database, or user input issues.
141///
142/// # Examples
143///
144/// ```bash
145/// # Create a new template interactively
146/// kasl template create
147///
148/// # Create template with specific name
149/// kasl template create --name daily-standup
150///
151/// # List all templates
152/// kasl template list
153///
154/// # Edit a template
155/// kasl template edit daily-standup
156///
157/// # Search for templates
158/// kasl template search meeting
159///
160/// # Interactive mode
161/// kasl template
162/// ```
163pub fn cmd(args: TemplateArgs) -> Result<()> {
164 match args.command {
165 Some(TemplateCommand::Create { name }) => handle_create(name),
166 Some(TemplateCommand::List) => handle_list(),
167 Some(TemplateCommand::Edit { name }) => handle_edit(name),
168 Some(TemplateCommand::Delete { name }) => handle_delete(name),
169 Some(TemplateCommand::Search { query }) => handle_search(query),
170 None => handle_interactive(),
171 }
172}
173
174/// Handles template creation with validation and uniqueness checking.
175///
176/// This function manages the complete template creation workflow:
177/// 1. **Name Collection**: Gets template name from args or interactive prompt
178/// 2. **Uniqueness Validation**: Ensures template name doesn't already exist
179/// 3. **Property Collection**: Gathers task name, comment, and completion values
180/// 4. **Validation**: Ensures all required fields are properly formatted
181/// 5. **Database Storage**: Saves the new template with proper error handling
182///
183/// ## Template Properties
184///
185/// Templates store these key properties:
186/// - **Name**: Unique identifier for referencing the template
187/// - **Task Name**: Default name for tasks created from this template
188/// - **Comment**: Default comment/description for tasks
189/// - **Completeness**: Default completion percentage (0-100)
190///
191/// ## Validation Rules
192///
193/// - Template names must be unique across the entire template library
194/// - Task names are required and cannot be empty
195/// - Completion values must be between 0 and 100 inclusive
196/// - Comments are optional and can be empty
197///
198/// # Arguments
199///
200/// * `name` - Optional template name from command line, or None for interactive prompt
201fn handle_create(name: Option<String>) -> Result<()> {
202 let mut templates_db = Templates::new()?;
203
204 // Get template name (from args or interactive prompt)
205 let name = name.unwrap_or_else(|| {
206 Input::with_theme(&ColorfulTheme::default())
207 .with_prompt(Message::PromptTemplateName.to_string())
208 .interact_text()
209 .unwrap()
210 });
211
212 // Validate template name uniqueness
213 if templates_db.exists(&name)? {
214 msg_error!(Message::TemplateAlreadyExists(name));
215 return Ok(());
216 }
217
218 // Collect template properties interactively
219 let task_name = Input::with_theme(&ColorfulTheme::default())
220 .with_prompt(Message::PromptTemplateTaskName.to_string())
221 .interact_text()?;
222
223 let comment = Input::with_theme(&ColorfulTheme::default())
224 .with_prompt(Message::PromptTemplateComment.to_string())
225 .allow_empty(true)
226 .interact_text()?;
227
228 let completeness_range_msg = Message::TaskCompletenessRange.to_string();
229 let completeness = Input::with_theme(&ColorfulTheme::default())
230 .with_prompt(Message::PromptTemplateCompleteness.to_string())
231 .default(100)
232 .validate_with(|input: &i32| -> Result<(), &str> {
233 if *input >= 0 && *input <= 100 { Ok(()) } else { Err(&completeness_range_msg) }
234 })
235 .interact_text()?;
236
237 // Create and save the template
238 let template = TaskTemplate::new(name.clone(), task_name, comment, completeness);
239 templates_db.create(&template)?;
240
241 msg_success!(Message::TemplateCreated(name));
242 Ok(())
243}
244
245/// Displays all available templates in a formatted table.
246///
247/// This function retrieves all templates from the database and presents
248/// them in a user-friendly table format showing all relevant properties.
249/// The display helps users understand their available templates and
250/// their configurations.
251///
252/// ## Display Format
253///
254/// The table includes these columns:
255/// - **Template Name**: Unique identifier for the template
256/// - **Task Name**: Default task name that will be used
257/// - **Comment**: Default comment or description
258/// - **Completeness**: Default completion percentage
259///
260/// ## Empty State Handling
261///
262/// When no templates exist, the function provides helpful guidance
263/// about creating the first template rather than displaying an empty table.
264fn handle_list() -> Result<()> {
265 let mut templates_db = Templates::new()?;
266 let templates = templates_db.get_all()?;
267
268 if templates.is_empty() {
269 msg_info!(Message::NoTemplatesFound);
270 return Ok(());
271 }
272
273 msg_print!(Message::TemplateListHeader, true);
274 View::templates(&templates)?;
275 Ok(())
276}
277
278/// Handles template editing with interactive or direct name specification.
279///
280/// This function provides comprehensive template editing capabilities:
281/// 1. **Template Selection**: Uses provided name or interactive selection
282/// 2. **Current State Display**: Shows existing template values
283/// 3. **Interactive Editing**: Prompts for new values with current values as defaults
284/// 4. **Validation**: Ensures edited values meet requirements
285/// 5. **Database Update**: Saves changes with proper error handling
286///
287/// ## Selection Methods
288///
289/// - **Direct**: When template name is provided via command line
290/// - **Interactive**: When no name is provided, presents selection interface
291///
292/// ## Editing Interface
293///
294/// For each editable property, the interface:
295/// - Shows the current value as the default
296/// - Allows the user to accept current value or enter new one
297/// - Validates new values according to template rules
298/// - Provides clear feedback about validation errors
299///
300/// # Arguments
301///
302/// * `name` - Optional template name to edit, or None for interactive selection
303fn handle_edit(name: Option<String>) -> Result<()> {
304 let mut templates_db = Templates::new()?;
305
306 // Get template name (direct or interactive selection)
307 let name = match name {
308 Some(n) => n,
309 None => {
310 let templates = templates_db.get_all()?;
311 if templates.is_empty() {
312 msg_info!(Message::NoTemplatesFound);
313 return Ok(());
314 }
315
316 let template_names: Vec<String> = templates.iter().map(|t| t.name.clone()).collect();
317 let selection = Select::with_theme(&ColorfulTheme::default())
318 .with_prompt(Message::SelectTemplateToEdit.to_string())
319 .items(&template_names)
320 .interact()?;
321
322 template_names[selection].clone()
323 }
324 };
325
326 // Fetch the template to edit
327 let template = match templates_db.get(&name)? {
328 Some(t) => t,
329 None => {
330 msg_error!(Message::TemplateNotFound(name));
331 return Ok(());
332 }
333 };
334
335 msg_print!(Message::EditingTemplate(template.name.clone()), true);
336
337 // Interactive editing with current values as defaults
338 let task_name = Input::with_theme(&ColorfulTheme::default())
339 .with_prompt(Message::PromptTemplateTaskName.to_string())
340 .default(template.task_name.clone())
341 .interact_text()?;
342
343 let comment = Input::with_theme(&ColorfulTheme::default())
344 .with_prompt(Message::PromptTemplateComment.to_string())
345 .default(template.comment.clone())
346 .allow_empty(true)
347 .interact_text()?;
348
349 let completeness_range_msg = Message::TaskCompletenessRange.to_string();
350 let completeness = Input::with_theme(&ColorfulTheme::default())
351 .with_prompt(Message::PromptTemplateCompleteness.to_string())
352 .default(template.completeness)
353 .validate_with(|input: &i32| -> Result<(), &str> {
354 if *input >= 0 && *input <= 100 { Ok(()) } else { Err(&completeness_range_msg) }
355 })
356 .interact_text()?;
357
358 // Update the template
359 let updated_template = TaskTemplate::new(name.clone(), task_name, comment, completeness);
360 templates_db.update(&updated_template)?;
361
362 msg_success!(Message::TemplateUpdated(name));
363 Ok(())
364}
365
366/// Handles safe template deletion with confirmation.
367///
368/// This function manages the template deletion process with appropriate
369/// safety measures to prevent accidental data loss:
370/// 1. **Template Selection**: Direct name or interactive selection
371/// 2. **Existence Validation**: Ensures template exists before attempting deletion
372/// 3. **Confirmation Prompt**: Requires explicit user confirmation
373/// 4. **Safe Deletion**: Removes template only after confirmation
374/// 5. **User Feedback**: Provides clear feedback about operation result
375///
376/// ## Safety Features
377///
378/// - Confirms template exists before showing deletion prompt
379/// - Uses clear, unambiguous confirmation language
380/// - Defaults to "No" for safety
381/// - Provides escape opportunity before actual deletion
382/// - Clear feedback about cancellation vs. completion
383///
384/// # Arguments
385///
386/// * `name` - Optional template name to delete, or None for interactive selection
387fn handle_delete(name: Option<String>) -> Result<()> {
388 let mut templates_db = Templates::new()?;
389
390 // Get template name (direct or interactive selection)
391 let name = match name {
392 Some(n) => n,
393 None => {
394 let templates = templates_db.get_all()?;
395 if templates.is_empty() {
396 msg_info!(Message::NoTemplatesFound);
397 return Ok(());
398 }
399
400 let template_names: Vec<String> = templates.iter().map(|t| t.name.clone()).collect();
401 let selection = Select::with_theme(&ColorfulTheme::default())
402 .with_prompt(Message::SelectTemplateToDelete.to_string())
403 .items(&template_names)
404 .interact()?;
405
406 template_names[selection].clone()
407 }
408 };
409
410 // Confirm deletion with user
411 let confirmed = Confirm::with_theme(&ColorfulTheme::default())
412 .with_prompt(Message::ConfirmDeleteTemplate(name.clone()).to_string())
413 .default(false)
414 .interact()?;
415
416 if confirmed {
417 templates_db.delete(&name)?;
418 msg_success!(Message::TemplateDeleted(name));
419 } else {
420 msg_info!(Message::OperationCancelled);
421 }
422
423 Ok(())
424}
425
426/// Handles template search functionality.
427///
428/// This function performs text-based searching across template names and
429/// task names, returning all matches in a formatted display. The search
430/// is case-insensitive and supports partial matching for user convenience.
431///
432/// ## Search Algorithm
433///
434/// The search functionality:
435/// - Performs case-insensitive matching
436/// - Searches both template names and task names
437/// - Supports partial string matching
438/// - Returns all matching templates
439/// - Displays results in standard template table format
440///
441/// ## Search Scope
442///
443/// The search covers these template fields:
444/// - **Template Name**: The unique identifier
445/// - **Task Name**: The default task name
446///
447/// Comments and completion values are not included in search to focus
448/// on the most relevant identifying information.
449///
450/// # Arguments
451///
452/// * `query` - Search string to match against template and task names
453fn handle_search(query: String) -> Result<()> {
454 let mut templates_db = Templates::new()?;
455 let templates = templates_db.search(&query)?;
456
457 if templates.is_empty() {
458 msg_info!(Message::NoTemplatesMatchingQuery(query));
459 return Ok(());
460 }
461
462 msg_print!(Message::TemplateSearchResults(query), true);
463 View::templates(&templates)?;
464 Ok(())
465}
466
467/// Handles interactive template management when no subcommand is provided.
468///
469/// This function provides a menu-driven interface for users who prefer
470/// interactive operation over command-line arguments. It presents all
471/// available template operations in an easy-to-navigate menu format.
472///
473/// ## Interactive Menu
474///
475/// The menu presents these options:
476/// 1. **Create new template**: Launches template creation workflow
477/// 2. **List templates**: Shows all available templates
478/// 3. **Edit template**: Template selection and editing interface
479/// 4. **Delete template**: Template selection and safe deletion
480///
481/// Each menu option delegates to the appropriate specialized handler
482/// function, ensuring consistent behavior between interactive and
483/// command-line usage.
484///
485/// ## User Experience
486///
487/// The interactive mode is designed for:
488/// - Users new to the command-line interface
489/// - Occasional template management tasks
490/// - Exploration of available template operations
491/// - Situations where remembering exact command syntax is inconvenient
492fn handle_interactive() -> Result<()> {
493 let options = vec!["Create new template", "List templates", "Edit template", "Delete template"];
494
495 let selection = Select::with_theme(&ColorfulTheme::default())
496 .with_prompt(Message::SelectTemplateAction.to_string())
497 .items(&options)
498 .interact()?;
499
500 match selection {
501 0 => handle_create(None),
502 1 => handle_list(),
503 2 => handle_edit(None),
504 3 => handle_delete(None),
505 _ => Ok(()),
506 }
507}