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::{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}