Skip to main content

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::{theme::ColorfulTheme, Confirm, Input, Select};
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 {
234                Ok(())
235            } else {
236                Err(&completeness_range_msg)
237            }
238        })
239        .interact_text()?;
240
241    // Create and save the template
242    let template = TaskTemplate::new(name.clone(), task_name, comment, completeness);
243    templates_db.create(&template)?;
244
245    msg_success!(Message::TemplateCreated(name));
246    Ok(())
247}
248
249/// Displays all available templates in a formatted table.
250///
251/// This function retrieves all templates from the database and presents
252/// them in a user-friendly table format showing all relevant properties.
253/// The display helps users understand their available templates and
254/// their configurations.
255///
256/// ## Display Format
257///
258/// The table includes these columns:
259/// - **Template Name**: Unique identifier for the template
260/// - **Task Name**: Default task name that will be used
261/// - **Comment**: Default comment or description
262/// - **Completeness**: Default completion percentage
263///
264/// ## Empty State Handling
265///
266/// When no templates exist, the function provides helpful guidance
267/// about creating the first template rather than displaying an empty table.
268fn handle_list() -> Result<()> {
269    let mut templates_db = Templates::new()?;
270    let templates = templates_db.get_all()?;
271
272    if templates.is_empty() {
273        msg_info!(Message::NoTemplatesFound);
274        return Ok(());
275    }
276
277    msg_print!(Message::TemplateListHeader, true);
278    View::templates(&templates)?;
279    Ok(())
280}
281
282/// Handles template editing with interactive or direct name specification.
283///
284/// This function provides comprehensive template editing capabilities:
285/// 1. **Template Selection**: Uses provided name or interactive selection
286/// 2. **Current State Display**: Shows existing template values
287/// 3. **Interactive Editing**: Prompts for new values with current values as defaults
288/// 4. **Validation**: Ensures edited values meet requirements
289/// 5. **Database Update**: Saves changes with proper error handling
290///
291/// ## Selection Methods
292///
293/// - **Direct**: When template name is provided via command line
294/// - **Interactive**: When no name is provided, presents selection interface
295///
296/// ## Editing Interface
297///
298/// For each editable property, the interface:
299/// - Shows the current value as the default
300/// - Allows the user to accept current value or enter new one
301/// - Validates new values according to template rules
302/// - Provides clear feedback about validation errors
303///
304/// # Arguments
305///
306/// * `name` - Optional template name to edit, or None for interactive selection
307fn handle_edit(name: Option<String>) -> Result<()> {
308    let mut templates_db = Templates::new()?;
309
310    // Get template name (direct or interactive selection)
311    let name = match name {
312        Some(n) => n,
313        None => {
314            let templates = templates_db.get_all()?;
315            if templates.is_empty() {
316                msg_info!(Message::NoTemplatesFound);
317                return Ok(());
318            }
319
320            let template_names: Vec<String> = templates.iter().map(|t| t.name.clone()).collect();
321            let selection = Select::with_theme(&ColorfulTheme::default())
322                .with_prompt(Message::SelectTemplateToEdit.to_string())
323                .items(&template_names)
324                .interact()?;
325
326            template_names[selection].clone()
327        }
328    };
329
330    // Fetch the template to edit
331    let template = match templates_db.get(&name)? {
332        Some(t) => t,
333        None => {
334            msg_error!(Message::TemplateNotFound(name));
335            return Ok(());
336        }
337    };
338
339    msg_print!(Message::EditingTemplate(template.name.clone()), true);
340
341    // Interactive editing with current values as defaults
342    let task_name = Input::with_theme(&ColorfulTheme::default())
343        .with_prompt(Message::PromptTemplateTaskName.to_string())
344        .default(template.task_name.clone())
345        .interact_text()?;
346
347    let comment = Input::with_theme(&ColorfulTheme::default())
348        .with_prompt(Message::PromptTemplateComment.to_string())
349        .default(template.comment.clone())
350        .allow_empty(true)
351        .interact_text()?;
352
353    let completeness_range_msg = Message::TaskCompletenessRange.to_string();
354    let completeness = Input::with_theme(&ColorfulTheme::default())
355        .with_prompt(Message::PromptTemplateCompleteness.to_string())
356        .default(template.completeness)
357        .validate_with(|input: &i32| -> Result<(), &str> {
358            if *input >= 0 && *input <= 100 {
359                Ok(())
360            } else {
361                Err(&completeness_range_msg)
362            }
363        })
364        .interact_text()?;
365
366    // Update the template
367    let updated_template = TaskTemplate::new(name.clone(), task_name, comment, completeness);
368    templates_db.update(&updated_template)?;
369
370    msg_success!(Message::TemplateUpdated(name));
371    Ok(())
372}
373
374/// Handles safe template deletion with confirmation.
375///
376/// This function manages the template deletion process with appropriate
377/// safety measures to prevent accidental data loss:
378/// 1. **Template Selection**: Direct name or interactive selection
379/// 2. **Existence Validation**: Ensures template exists before attempting deletion
380/// 3. **Confirmation Prompt**: Requires explicit user confirmation
381/// 4. **Safe Deletion**: Removes template only after confirmation
382/// 5. **User Feedback**: Provides clear feedback about operation result
383///
384/// ## Safety Features
385///
386/// - Confirms template exists before showing deletion prompt
387/// - Uses clear, unambiguous confirmation language
388/// - Defaults to "No" for safety
389/// - Provides escape opportunity before actual deletion
390/// - Clear feedback about cancellation vs. completion
391///
392/// # Arguments
393///
394/// * `name` - Optional template name to delete, or None for interactive selection
395fn handle_delete(name: Option<String>) -> Result<()> {
396    let mut templates_db = Templates::new()?;
397
398    // Get template name (direct or interactive selection)
399    let name = match name {
400        Some(n) => n,
401        None => {
402            let templates = templates_db.get_all()?;
403            if templates.is_empty() {
404                msg_info!(Message::NoTemplatesFound);
405                return Ok(());
406            }
407
408            let template_names: Vec<String> = templates.iter().map(|t| t.name.clone()).collect();
409            let selection = Select::with_theme(&ColorfulTheme::default())
410                .with_prompt(Message::SelectTemplateToDelete.to_string())
411                .items(&template_names)
412                .interact()?;
413
414            template_names[selection].clone()
415        }
416    };
417
418    // Confirm deletion with user
419    let confirmed = Confirm::with_theme(&ColorfulTheme::default())
420        .with_prompt(Message::ConfirmDeleteTemplate(name.clone()).to_string())
421        .default(false)
422        .interact()?;
423
424    if confirmed {
425        templates_db.delete(&name)?;
426        msg_success!(Message::TemplateDeleted(name));
427    } else {
428        msg_info!(Message::OperationCancelled);
429    }
430
431    Ok(())
432}
433
434/// Handles template search functionality.
435///
436/// This function performs text-based searching across template names and
437/// task names, returning all matches in a formatted display. The search
438/// is case-insensitive and supports partial matching for user convenience.
439///
440/// ## Search Algorithm
441///
442/// The search functionality:
443/// - Performs case-insensitive matching
444/// - Searches both template names and task names
445/// - Supports partial string matching
446/// - Returns all matching templates
447/// - Displays results in standard template table format
448///
449/// ## Search Scope
450///
451/// The search covers these template fields:
452/// - **Template Name**: The unique identifier
453/// - **Task Name**: The default task name
454///
455/// Comments and completion values are not included in search to focus
456/// on the most relevant identifying information.
457///
458/// # Arguments
459///
460/// * `query` - Search string to match against template and task names
461fn handle_search(query: String) -> Result<()> {
462    let mut templates_db = Templates::new()?;
463    let templates = templates_db.search(&query)?;
464
465    if templates.is_empty() {
466        msg_info!(Message::NoTemplatesMatchingQuery(query));
467        return Ok(());
468    }
469
470    msg_print!(Message::TemplateSearchResults(query), true);
471    View::templates(&templates)?;
472    Ok(())
473}
474
475/// Handles interactive template management when no subcommand is provided.
476///
477/// This function provides a menu-driven interface for users who prefer
478/// interactive operation over command-line arguments. It presents all
479/// available template operations in an easy-to-navigate menu format.
480///
481/// ## Interactive Menu
482///
483/// The menu presents these options:
484/// 1. **Create new template**: Launches template creation workflow
485/// 2. **List templates**: Shows all available templates
486/// 3. **Edit template**: Template selection and editing interface
487/// 4. **Delete template**: Template selection and safe deletion
488///
489/// Each menu option delegates to the appropriate specialized handler
490/// function, ensuring consistent behavior between interactive and
491/// command-line usage.
492///
493/// ## User Experience
494///
495/// The interactive mode is designed for:
496/// - Users new to the command-line interface
497/// - Occasional template management tasks
498/// - Exploration of available template operations
499/// - Situations where remembering exact command syntax is inconvenient
500fn handle_interactive() -> Result<()> {
501    let options = vec!["Create new template", "List templates", "Edit template", "Delete template"];
502
503    let selection = Select::with_theme(&ColorfulTheme::default())
504        .with_prompt(Message::SelectTemplateAction.to_string())
505        .items(&options)
506        .interact()?;
507
508    match selection {
509        0 => handle_create(None),
510        1 => handle_list(),
511        2 => handle_edit(None),
512        3 => handle_delete(None),
513        _ => Ok(()),
514    }
515}