kasl/libs/task.rs
1//! Task management and manipulation functionality for productivity tracking.
2//!
3//! Provides comprehensive task management capabilities including task creation,
4//! modification, filtering, and formatting for organizing work items.
5//!
6//! ## Features
7//!
8//! - **Task Structure**: Identification, description, progress tracking, categorization
9//! - **Filtering System**: Date-based, completion status, ID-based, tag-based filtering
10//! - **Formatting**: Console table rendering, export formatting, template support
11//! - **Integration**: Jira issues, GitLab commits, manual entry, template instantiation
12//! - **Database Integration**: CRUD operations, transaction safety, relationship management
13//!
14//! ## Usage
15//!
16//! ```rust
17//! use kasl::libs::task::Task;
18//!
19//! let task = Task::new(
20//! "Implement user authentication",
21//! "Add OAuth2 integration with Google and GitHub",
22//! Some(25)
23//! );
24//! ```
25
26use crate::db::tags::Tag;
27use chrono::NaiveDate;
28
29/// Represents a single task or work item in the productivity tracking system.
30///
31/// The Task struct encapsulates all information about a work item including
32/// its identification, description, progress status, and associated metadata.
33/// Tasks serve as the fundamental unit of work organization within kasl.
34///
35/// ## Field Descriptions
36///
37/// ### Identification Fields
38/// - `id`: Database primary key for persistence and relationships
39/// - `task_id`: Reference ID for linking to external systems or parent tasks
40/// - `timestamp`: Creation/modification time for audit trails
41///
42/// ### Content Fields
43/// - `name`: Brief, descriptive title for the task
44/// - `comment`: Detailed description, notes, or additional context
45/// - `completeness`: Progress percentage (0-100) indicating work completion
46///
47/// ### Configuration Fields
48/// - `excluded_from_search`: Flag to hide tasks from general searches
49/// - `tags`: Collection of categorization labels for organization
50///
51/// ## Usage Patterns
52///
53/// ### New Task Creation
54/// ```rust
55/// let task = Task::new(
56/// "Code review for PR #123",
57/// "Review authentication changes and security implications",
58/// Some(0) // Just started
59/// );
60/// ```
61///
62/// ### Task Updates
63/// ```rust
64/// let mut task = existing_task;
65/// task.completeness = Some(75); // 75% complete
66/// task.comment = "Almost finished, testing remaining".to_string();
67/// ```
68///
69/// ### External Integration
70/// ```rust
71/// let jira_task = Task {
72/// id: None, // Will be assigned by database
73/// task_id: Some(jira_issue_id),
74/// name: jira_issue.summary,
75/// comment: jira_issue.description.unwrap_or_default(),
76/// completeness: Some(100), // Imported completed issues
77/// // ... other fields
78/// };
79/// ```
80#[derive(Debug, Clone)]
81pub struct Task {
82 /// Database primary key for task identification and relationships.
83 ///
84 /// This field contains the unique identifier assigned by the database
85 /// when the task is first saved. It's used for:
86 /// - Database queries and updates
87 /// - Foreign key relationships (tags, time tracking)
88 /// - Cross-referencing with other application data
89 ///
90 /// **Values:**
91 /// - `Some(id)`: Task exists in database with assigned ID
92 /// - `None`: New task not yet saved to database
93 pub id: Option<i32>,
94
95 /// Reference identifier for external system integration or task linking.
96 ///
97 /// This field provides a way to link tasks to external systems or
98 /// create hierarchical relationships between tasks. Common uses include:
99 /// - Jira issue numbers for imported tasks
100 /// - GitLab merge request IDs for code review tasks
101 /// - Parent task IDs for subtask relationships
102 /// - External project management system references
103 ///
104 /// **Values:**
105 /// - `Some(id)`: Task is linked to external system or parent task
106 /// - `None`: Standalone task with no external references
107 pub task_id: Option<i32>,
108
109 /// ISO 8601 timestamp string indicating task creation or last modification.
110 ///
111 /// This field provides audit trail information for task management.
112 /// The timestamp is automatically managed by the database layer and
113 /// is primarily used for:
114 /// - Sorting tasks by creation or modification time
115 /// - Audit trails and change tracking
116 /// - Time-based filtering and reporting
117 /// - Synchronization with external systems
118 ///
119 /// **Format:** "YYYY-MM-DD HH:MM:SS" in local timezone
120 /// **Example:** "2025-01-15 14:30:45"
121 pub timestamp: Option<String>,
122
123 /// Brief, descriptive title summarizing the task's purpose or objective.
124 ///
125 /// The task name should be concise yet descriptive enough to understand
126 /// the work item at a glance. It appears in task lists, reports, and
127 /// notifications throughout the application.
128 ///
129 /// **Guidelines:**
130 /// - Keep under 100 characters for display compatibility
131 /// - Use action-oriented language ("Implement", "Review", "Fix")
132 /// - Include key context ("Fix login bug in mobile app")
133 /// - Avoid technical jargon when possible
134 ///
135 /// **Examples:**
136 /// - "Implement OAuth2 authentication"
137 /// - "Review security audit findings"
138 /// - "Update API documentation for v2.0"
139 pub name: String,
140
141 /// Detailed description, notes, or additional context for the task.
142 ///
143 /// The comment field provides space for detailed information that doesn't
144 /// fit in the task name. This might include:
145 /// - Technical requirements and specifications
146 /// - Links to related resources or documentation
147 /// - Progress notes and status updates
148 /// - Dependencies and prerequisites
149 /// - Testing criteria and acceptance conditions
150 ///
151 /// **Content Guidelines:**
152 /// - Use clear, structured formatting when helpful
153 /// - Include links to relevant resources
154 /// - Update with progress notes and findings
155 /// - Keep information current and relevant
156 pub comment: String,
157
158 /// Completion percentage indicating task progress from 0% to 100%.
159 ///
160 /// This field tracks the task's progress through its lifecycle,
161 /// providing quantitative measurement of work completion. The percentage
162 /// is used for:
163 /// - Progress reporting and analytics
164 /// - Filtering incomplete vs. complete tasks
165 /// - Productivity calculations and trends
166 /// - Project status tracking and reporting
167 ///
168 /// **Values:**
169 /// - `Some(0)`: Task not started
170 /// - `Some(1-99)`: Task in progress
171 /// - `Some(100)`: Task completed
172 /// - `None`: Progress not tracked or unknown
173 ///
174 /// **Default:** 100% for tasks imported from external systems
175 pub completeness: Option<i32>,
176
177 /// Flag indicating whether task should be hidden from general searches.
178 ///
179 /// This field allows tasks to be excluded from default search results
180 /// while remaining accessible through direct queries. Useful for:
181 /// - Administrative or system-generated tasks
182 /// - Deprecated or obsolete tasks that shouldn't appear in normal workflow
183 /// - Sensitive tasks that require explicit access
184 /// - Archived tasks that should remain searchable but not prominent
185 ///
186 /// **Values:**
187 /// - `Some(true)`: Task excluded from general searches
188 /// - `Some(false)` or `None`: Task included in normal search results
189 pub excluded_from_search: Option<bool>,
190
191 /// Collection of categorization tags associated with this task.
192 ///
193 /// Tags provide a flexible labeling system for task organization and
194 /// filtering. They enable:
195 /// - Project-based organization ("frontend", "backend", "mobile")
196 /// - Priority classification ("urgent", "low-priority", "nice-to-have")
197 /// - Status indicators ("blocked", "waiting-review", "approved")
198 /// - Skill-based categorization ("javascript", "database", "ui-design")
199 ///
200 /// **Management:**
201 /// - Tags are created automatically when first used
202 /// - Multiple tags can be associated with a single task
203 /// - Tag associations are maintained in separate database table
204 /// - Tags can be deleted if no longer associated with any tasks
205 pub tags: Vec<Tag>,
206}
207
208impl Task {
209 /// Creates a new task with the specified name, comment, and completion status.
210 ///
211 /// This constructor initializes a new task with the provided information
212 /// and sets appropriate defaults for other fields. The task is not
213 /// automatically saved to the database - use the database layer for persistence.
214 ///
215 /// ## Default Values
216 ///
217 /// New tasks are initialized with:
218 /// - `id`: None (assigned when saved to database)
219 /// - `task_id`: None (no external reference)
220 /// - `timestamp`: None (managed by database)
221 /// - `excluded_from_search`: None (included in searches)
222 /// - `tags`: Empty vector (no initial categorization)
223 ///
224 /// ## Parameter Guidelines
225 ///
226 /// ### Task Name
227 /// - Should be concise but descriptive
228 /// - Use action-oriented language
229 /// - Include key context for clarity
230 ///
231 /// ### Comment
232 /// - Provide detailed context and requirements
233 /// - Include links to related resources
234 /// - Can be updated as work progresses
235 ///
236 /// ### Completeness
237 /// - Use `Some(0)` for new tasks that haven't started
238 /// - Use `Some(100)` for already completed imported tasks
239 /// - Use `None` if progress tracking isn't needed
240 ///
241 /// # Arguments
242 ///
243 /// * `name` - Brief, descriptive task title
244 /// * `comment` - Detailed description or notes
245 /// * `completeness` - Optional completion percentage (0-100)
246 ///
247 /// # Returns
248 ///
249 /// A new Task instance ready for use or database persistence.
250 ///
251 /// # Examples
252 ///
253 /// ```rust
254 /// use kasl::libs::task::Task;
255 ///
256 /// // Create a new task that's just starting
257 /// let new_task = Task::new(
258 /// "Implement user registration",
259 /// "Add email verification and password validation",
260 /// Some(0)
261 /// );
262 ///
263 /// // Create a completed task (e.g., imported from external system)
264 /// let completed_task = Task::new(
265 /// "Fix login redirect bug",
266 /// "Resolved issue with OAuth callback URL handling",
267 /// Some(100)
268 /// );
269 ///
270 /// // Create a task without progress tracking
271 /// let planning_task = Task::new(
272 /// "Research authentication libraries",
273 /// "Evaluate OAuth2 libraries for Node.js backend",
274 /// None
275 /// );
276 /// ```
277 pub fn new(name: &str, comment: &str, completeness: Option<i32>) -> Self {
278 Task {
279 id: None,
280 task_id: None,
281 timestamp: None,
282 name: collapse_whitespace(name),
283 comment: collapse_whitespace(comment),
284 completeness,
285 excluded_from_search: None,
286 tags: Vec::new(),
287 }
288 }
289
290 /// Updates task fields from another task while preserving identity fields.
291 ///
292 /// This method provides a convenient way to update task content while
293 /// maintaining database identity and relationships. It's particularly
294 /// useful for implementing task editing workflows where the user modifies
295 /// task details but the task's core identity remains unchanged.
296 ///
297 /// ## Preserved Fields
298 /// The following fields are **not** updated to maintain task identity:
299 /// - `id`: Database primary key remains unchanged
300 /// - `task_id`: External reference remains unchanged
301 /// - `timestamp`: Will be updated by database on save
302 /// - `excluded_from_search`: Search visibility remains unchanged
303 /// - `tags`: Tag associations require separate management
304 ///
305 /// ## Updated Fields
306 /// The following fields are copied from the source task:
307 /// - `name`: Task title is updated
308 /// - `comment`: Description and notes are updated
309 /// - `completeness`: Progress percentage is updated
310 ///
311 /// ## Use Cases
312 ///
313 /// ### Task Editing Workflow
314 /// ```rust
315 /// // Load existing task from database
316 /// let mut existing_task = tasks_db.get_by_id(42)?;
317 ///
318 /// // Create updated version with user modifications
319 /// let updated_task = Task::new(
320 /// "Updated task name",
321 /// "Updated description with new requirements",
322 /// Some(75)
323 /// );
324 ///
325 /// // Apply updates while preserving identity
326 /// existing_task.update_from(&updated_task);
327 ///
328 /// // Save to database
329 /// tasks_db.update(&existing_task)?;
330 /// ```
331 ///
332 /// ### Bulk Task Updates
333 /// ```rust
334 /// for mut task in tasks_to_update {
335 /// if let Some(template) = get_update_template(&task) {
336 /// task.update_from(&template);
337 /// tasks_db.update(&task)?;
338 /// }
339 /// }
340 /// ```
341 ///
342 /// # Arguments
343 ///
344 /// * `other` - Source task containing the updated field values
345 ///
346 /// # Examples
347 ///
348 /// ```rust
349 /// use kasl::libs::task::Task;
350 ///
351 /// let mut original_task = Task::new(
352 /// "Original task",
353 /// "Original description",
354 /// Some(25)
355 /// );
356 /// // Simulate database assignment
357 /// original_task.id = Some(42);
358 ///
359 /// let updated_task = Task::new(
360 /// "Updated task name",
361 /// "Updated description with more details",
362 /// Some(75)
363 /// );
364 ///
365 /// // Apply updates while preserving ID
366 /// original_task.update_from(&updated_task);
367 ///
368 /// assert_eq!(original_task.id, Some(42)); // ID preserved
369 /// assert_eq!(original_task.name, "Updated task name"); // Content updated
370 /// assert_eq!(original_task.completeness, Some(75)); // Progress updated
371 /// ```
372 pub fn update_from(&mut self, other: &Task) {
373 // Update content fields while preserving identity
374 self.name = other.name.clone();
375 self.comment = other.comment.clone();
376 self.completeness = other.completeness;
377 }
378}
379
380/// Enumeration of available task filtering criteria for database queries.
381///
382/// This enum provides a type-safe way to specify different filtering options
383/// when querying tasks from the database. It supports simple filters as well
384/// as complex multi-criteria filtering for advanced task management workflows.
385///
386/// ## Filter Categories
387///
388/// ### Scope Filters
389/// - `All`: No filtering, returns all tasks
390/// - `Date`: Time-based filtering for specific dates
391///
392/// ### Status Filters
393/// - `Incomplete`: Tasks with progress less than 100%
394///
395/// ### Identity Filters
396/// - `ByIds`: Specific tasks by their database IDs
397///
398/// ### Categorization Filters
399/// - `ByTag`: Tasks associated with a single tag
400/// - `ByTags`: Tasks associated with multiple tags (intersection)
401///
402/// ## Query Optimization
403///
404/// Different filter types have different performance characteristics:
405/// - `All`: Fastest, no WHERE clause needed
406/// - `ByIds`: Very fast with proper indexing
407/// - `Date`: Fast with timestamp indexing
408/// - `Incomplete`: Moderate speed, depends on data distribution
409/// - `ByTag`/`ByTags`: Moderate speed, requires JOIN operations
410///
411/// ## Examples Usage
412///
413/// ```rust
414/// use kasl::libs::task::TaskFilter;
415/// use chrono::Local;
416///
417/// // Get all tasks
418/// let all_tasks_filter = TaskFilter::All;
419///
420/// // Get today's tasks
421/// let today = Local::now().date_naive();
422/// let today_filter = TaskFilter::Date(today);
423///
424/// // Get incomplete tasks
425/// let incomplete_filter = TaskFilter::Incomplete;
426///
427/// // Get specific tasks
428/// let specific_filter = TaskFilter::ByIds(vec![1, 2, 3]);
429///
430/// // Get tagged tasks
431/// let tagged_filter = TaskFilter::ByTag("urgent".to_string());
432/// let multi_tagged_filter = TaskFilter::ByTags(vec![
433/// "frontend".to_string(),
434/// "javascript".to_string()
435/// ]);
436/// ```
437#[derive(Debug, Clone)]
438pub enum TaskFilter {
439 /// Returns all tasks without any filtering restrictions.
440 ///
441 /// This filter retrieves the complete task collection from the database.
442 /// It's the most efficient filter type since it doesn't require any
443 /// WHERE clauses or complex query conditions.
444 ///
445 /// **Use Cases:**
446 /// - Complete task listings for administrative purposes
447 /// - Full data exports and backups
448 /// - Global task analysis and reporting
449 /// - Initial load for client-side filtering
450 ///
451 /// **Performance:** Excellent - simple SELECT query
452 All,
453
454 /// Returns tasks created or modified on the specified date.
455 ///
456 /// This filter uses the task timestamp to find tasks associated with
457 /// a particular date. The filtering is typically done at the day level,
458 /// including all tasks from 00:00:00 to 23:59:59 on the specified date.
459 ///
460 /// **Use Cases:**
461 /// - Daily task reviews and reports
462 /// - Time-based productivity analysis
463 /// - Date-specific task exports
464 /// - Calendar integration and scheduling
465 ///
466 /// **Performance:** Good - benefits from timestamp indexing
467 ///
468 /// **Example:**
469 /// ```rust
470 /// use kasl::libs::task::TaskFilter;
471 /// use chrono::{Local, NaiveDate};
472 ///
473 /// let today = Local::now().date_naive();
474 /// let filter = TaskFilter::Date(today);
475 /// ```
476 Date(NaiveDate),
477
478 /// Returns tasks with completion percentage less than 100%.
479 ///
480 /// This filter identifies tasks that are still in progress or haven't
481 /// been started. It's useful for focusing on active work items and
482 /// identifying tasks that need attention.
483 ///
484 /// **Criteria:**
485 /// - Tasks with `completeness` < 100
486 /// - Tasks with `completeness` = None (treated as incomplete)
487 ///
488 /// **Use Cases:**
489 /// - Active work item management
490 /// - Progress tracking and follow-up
491 /// - Workload planning and estimation
492 /// - Focus mode filtering for current work
493 ///
494 /// **Performance:** Moderate - depends on completion data distribution
495 ///
496 /// **Example:**
497 /// ```rust
498 /// use kasl::libs::task::TaskFilter;
499 ///
500 /// let incomplete_filter = TaskFilter::Incomplete;
501 /// // Returns tasks with completeness: None, Some(0), Some(50), etc.
502 /// // Excludes tasks with completeness: Some(100)
503 /// ```
504 Incomplete,
505
506 /// Returns specific tasks identified by their database IDs.
507 ///
508 /// This filter provides precise task retrieval when the exact task
509 /// identifiers are known. It's the most efficient way to retrieve
510 /// a known set of tasks and is commonly used for bulk operations.
511 ///
512 /// **Use Cases:**
513 /// - Bulk task operations (update, delete, export)
514 /// - User-selected task collections
515 /// - Related task loading (parent/child relationships)
516 /// - API responses for specific task requests
517 ///
518 /// **Performance:** Excellent - uses primary key indexing
519 ///
520 /// **Example:**
521 /// ```rust
522 /// use kasl::libs::task::TaskFilter;
523 ///
524 /// let specific_tasks = TaskFilter::ByIds(vec![1, 5, 10, 15]);
525 /// // Returns only tasks with IDs 1, 5, 10, and 15
526 /// ```
527 ByIds(Vec<i32>),
528
529 /// Returns tasks associated with the specified tag.
530 ///
531 /// This filter finds all tasks that have been tagged with a particular
532 /// label. It's useful for category-based task management and organizing
533 /// work by project, priority, or skill area.
534 ///
535 /// **Query Method:**
536 /// - Performs JOIN with task_tags relationship table
537 /// - Matches tag name case-sensitively
538 /// - Returns tasks with at least one matching tag
539 ///
540 /// **Use Cases:**
541 /// - Project-specific task listings
542 /// - Priority-based filtering ("urgent", "low-priority")
543 /// - Skill-based work organization ("javascript", "database")
544 /// - Status-based filtering ("blocked", "waiting-review")
545 ///
546 /// **Performance:** Moderate - requires JOIN operation
547 ///
548 /// **Example:**
549 /// ```rust
550 /// use kasl::libs::task::TaskFilter;
551 ///
552 /// let urgent_filter = TaskFilter::ByTag("urgent".to_string());
553 /// // Returns all tasks tagged with "urgent"
554 /// ```
555 ByTag(String),
556
557 /// Returns tasks associated with all of the specified tags.
558 ///
559 /// This filter finds tasks that have been tagged with every tag in the
560 /// provided list (intersection, not union). It's useful for finding tasks
561 /// that meet multiple criteria simultaneously.
562 ///
563 /// **Query Method:**
564 /// - Performs multiple JOINs with task_tags table
565 /// - Requires ALL tags to be present on the task
566 /// - More restrictive than single tag filtering
567 ///
568 /// **Use Cases:**
569 /// - Complex filtering ("frontend" AND "urgent" AND "javascript")
570 /// - Multi-criteria task discovery
571 /// - Advanced search functionality
572 /// - Refined project management workflows
573 ///
574 /// **Performance:** Moderate to Slow - multiple JOINs required
575 ///
576 /// **Example:**
577 /// ```rust
578 /// use kasl::libs::task::TaskFilter;
579 ///
580 /// let complex_filter = TaskFilter::ByTags(vec![
581 /// "frontend".to_string(),
582 /// "urgent".to_string(),
583 /// "javascript".to_string()
584 /// ]);
585 /// // Returns tasks that have ALL three tags
586 /// ```
587 ByTags(Vec<String>),
588}
589
590/// Trait providing formatting and manipulation operations for task collections.
591///
592/// This trait extends Vec<Task> with specialized methods for formatting tasks
593/// for display and dividing task collections for parallel processing or
594/// load balancing. It provides a clean interface for common task collection
595/// operations.
596///
597/// ## Design Philosophy
598///
599/// The trait follows Rust's iterator philosophy by providing chainable,
600/// efficient operations on task collections. Methods are designed to be:
601/// - **Composable**: Can be chained together for complex operations
602/// - **Efficient**: Minimize allocations and copying where possible
603/// - **Flexible**: Support various output formats and processing patterns
604/// - **Predictable**: Consistent behavior across different input sizes
605///
606/// ## Method Categories
607///
608/// ### Formatting Methods
609/// - `format()`: Convert tasks to human-readable string representation
610///
611/// ### Partitioning Methods
612/// - `divide()`: Split tasks into balanced groups for parallel processing
613///
614/// ## Performance Characteristics
615///
616/// - **Memory Usage**: Methods minimize unnecessary allocations
617/// - **Time Complexity**: Most operations are O(n) where n is task count
618/// - **Parallelization**: Partitioning methods support concurrent processing
619///
620/// ## Examples
621///
622/// ```rust
623/// use kasl::libs::task::{Task, FormatTasks};
624///
625/// let mut tasks = vec![
626/// Task::new("Task 1", "Description 1", Some(50)),
627/// Task::new("Task 2", "Description 2", Some(75)),
628/// Task::new("Task 3", "Description 3", Some(100)),
629/// ];
630///
631/// // Format for display
632/// let formatted = tasks.format();
633/// println!("{}", formatted);
634///
635/// // Divide for parallel processing
636/// let groups = tasks.divide(2);
637/// for (i, group) in groups.iter().enumerate() {
638/// println!("Group {}: {} tasks", i, group.len());
639/// }
640/// ```
641pub trait FormatTasks {
642 /// Formats the task collection into a human-readable string representation.
643 ///
644 /// This method converts a collection of tasks into a structured string
645 /// format suitable for console output, logging, or simple text-based
646 /// displays. The format includes key task information in a consistent,
647 /// scannable layout.
648 ///
649 /// ## Output Format
650 ///
651 /// The method produces a multi-line string with each task formatted as:
652 /// ```text
653 /// {name} ({completeness}%)
654 /// ```
655 ///
656 /// ## Field Handling
657 ///
658 /// - **ID**: Shows database ID or "New" for unsaved tasks
659 /// - **Name**: Task title, truncated if excessively long
660 /// - **Completeness**: Percentage or "Unknown" if not set
661 /// - **Comment**: Description, truncated if excessively long
662 ///
663 /// ## Use Cases
664 ///
665 /// - **Debug Output**: Quick task collection visualization
666 /// - **Log Messages**: Structured logging of task operations
667 /// - **Simple Reports**: Basic text-based task summaries
668 /// - **CLI Output**: Command-line interface task displays
669 ///
670 /// # Returns
671 ///
672 /// A formatted string representation of all tasks in the collection.
673 ///
674 /// # Examples
675 ///
676 /// ```rust
677 /// use kasl::libs::task::{Task, FormatTasks};
678 ///
679 /// let mut tasks = vec![
680 /// Task::new("Review PR", "Code review for auth changes", Some(25)),
681 /// Task::new("Write tests", "Unit tests for API endpoints", Some(75)),
682 /// ];
683 ///
684 /// let output = tasks.format();
685 /// // Output:
686 /// // Review PR (25%)
687 /// // Write tests (75%)
688 /// ```
689 fn format(&mut self) -> String;
690
691 /// Divides the task collection into the specified number of balanced groups.
692 ///
693 /// This method partitions tasks into multiple groups of approximately equal
694 /// size, which is useful for parallel processing, load balancing, or
695 /// organizing large task collections into manageable chunks.
696 ///
697 /// ## Partitioning Algorithm
698 ///
699 /// The method uses a round-robin distribution strategy:
700 /// 1. **Base Size Calculation**: Determines minimum tasks per group
701 /// 2. **Remainder Distribution**: Distributes extra tasks evenly
702 /// 3. **Sequential Assignment**: Assigns tasks to groups in order
703 /// 4. **Balance Optimization**: Ensures groups differ by at most 1 task
704 ///
705 /// ## Edge Case Handling
706 ///
707 /// ### Empty Collection
708 /// - Returns vector of empty groups
709 /// - Number of groups equals requested parts
710 ///
711 /// ### Single Task
712 /// - Duplicates the task across all groups
713 /// - Useful for broadcast scenarios
714 ///
715 /// ### Fewer Tasks Than Parts
716 /// - Creates groups with 0-1 tasks each
717 /// - Distributes tasks round-robin style
718 ///
719 /// ### More Tasks Than Parts
720 /// - Creates balanced groups with similar sizes
721 /// - Groups differ by at most 1 task
722 ///
723 /// ## Use Cases
724 ///
725 /// ### Parallel Processing
726 /// ```rust
727 /// let task_groups = tasks.divide(cpu_count);
728 /// for group in task_groups {
729 /// spawn_worker_thread(group);
730 /// }
731 /// ```
732 ///
733 /// ### Load Balancing
734 /// ```rust
735 /// let worker_assignments = tasks.divide(worker_count);
736 /// for (worker_id, assignment) in worker_assignments.iter().enumerate() {
737 /// assign_tasks_to_worker(worker_id, assignment);
738 /// }
739 /// ```
740 ///
741 /// ### UI Organization
742 /// ```rust
743 /// let columns = tasks.divide(3); // Three-column layout
744 /// for (col_index, column_tasks) in columns.iter().enumerate() {
745 /// render_task_column(col_index, column_tasks);
746 /// }
747 /// ```
748 ///
749 /// # Arguments
750 ///
751 /// * `parts` - Number of groups to create (must be > 0)
752 ///
753 /// # Returns
754 ///
755 /// A vector containing the requested number of task groups. Each group
756 /// is a Vec<Task> containing a portion of the original task collection.
757 ///
758 /// # Examples
759 ///
760 /// ```rust
761 /// use kasl::libs::task::{Task, FormatTasks};
762 ///
763 /// let mut tasks = vec![
764 /// Task::new("Task 1", "", None),
765 /// Task::new("Task 2", "", None),
766 /// Task::new("Task 3", "", None),
767 /// Task::new("Task 4", "", None),
768 /// Task::new("Task 5", "", None),
769 /// ];
770 ///
771 /// // Divide into 3 groups
772 /// let groups = tasks.divide(3);
773 /// // groups[0]: [Task 1, Task 4] (2 tasks)
774 /// // groups[1]: [Task 2, Task 5] (2 tasks)
775 /// // groups[2]: [Task 3] (1 task)
776 ///
777 /// // Verify balanced distribution
778 /// assert_eq!(groups.len(), 3);
779 /// assert_eq!(groups[0].len(), 2);
780 /// assert_eq!(groups[1].len(), 2);
781 /// assert_eq!(groups[2].len(), 1);
782 /// ```
783 fn divide(&mut self, parts: usize) -> Vec<Vec<Task>>;
784}
785
786/// Implementation of FormatTasks trait for Vec<Task>.
787///
788/// This implementation provides concrete formatting and partitioning logic
789/// for task collections. It handles various edge cases and provides efficient
790/// algorithms for common task manipulation scenarios.
791impl FormatTasks for Vec<Task> {
792 /// Divides the task collection into balanced groups using round-robin distribution.
793 ///
794 /// This implementation uses an optimized algorithm that ensures balanced
795 /// distribution while handling edge cases gracefully. The algorithm
796 /// minimizes memory allocations and provides predictable results.
797 ///
798 /// ## Algorithm Details
799 ///
800 /// 1. **Input Validation**: Handle zero parts and empty collections
801 /// 2. **Special Cases**: Optimize for single task and small collections
802 /// 3. **Size Calculation**: Compute base size and remainder distribution
803 /// 4. **Group Assignment**: Distribute tasks using calculated sizes
804 ///
805 /// ## Performance Characteristics
806 ///
807 /// - **Time Complexity**: O(n) where n is the number of tasks
808 /// - **Space Complexity**: O(n) for the output groups
809 /// - **Memory Efficiency**: Minimal allocations during processing
810 ///
811 /// The implementation is optimized for common use cases while maintaining
812 /// correctness for edge cases.
813 fn divide(&mut self, parts: usize) -> Vec<Vec<Task>> {
814 // Initialize result vector with requested capacity
815 let mut result: Vec<Vec<Task>> = Vec::with_capacity(parts);
816 let len = self.len();
817
818 // Handle edge case: no parts requested
819 if len == 0 || parts == 0 {
820 return result;
821 }
822
823 // Handle edge case: single task
824 if len == 1 {
825 for _ in 0..parts {
826 result.push(self.to_vec());
827 }
828 return result;
829 }
830
831 // Handle edge case: fewer tasks than parts
832 if len < parts {
833 for i in 0..parts {
834 let mut part: Vec<Task> = Vec::with_capacity(len.div_ceil(parts));
835 for j in 0..len.div_ceil(parts) {
836 part.push(self[(i + j * len / parts) % len].clone());
837 }
838 result.push(part);
839 }
840 return result;
841 }
842
843 // General case: distribute tasks across parts
844 let mut start = 0;
845 let mut end;
846 for i in 0..parts {
847 // Calculate group size with remainder distribution
848 end = start + len / parts + if i < len % parts { 1 } else { 0 };
849 result.push(self[start..end].to_vec());
850 start = end;
851 }
852
853 result
854 }
855
856 /// Formats the task collection into a structured string representation.
857 ///
858 /// This implementation creates a multi-line string with each task formatted
859 /// consistently. It handles missing fields gracefully and provides readable
860 /// output suitable for debugging and simple displays.
861 ///
862 /// ## Format Structure
863 ///
864 /// Each task is formatted on a separate line with pipe-separated fields:
865 /// ```text
866 /// {name} ({completeness}%)
867 /// ```
868 ///
869 /// ## Field Processing
870 ///
871 /// - **Name**: Used as-is from task struct
872 /// - **Completeness**: Shows percentage or "Unknown" for None values
873 ///
874 /// The method handles all field types gracefully and provides consistent
875 /// output regardless of which optional fields are present.
876 fn format(&mut self) -> String {
877 self.iter()
878 .map(|task| {
879 // Format completeness field
880 let completeness_display = task.completeness.map_or("Unknown".to_string(), |comp| format!("{}%", comp));
881
882 // Create formatted line for this task
883 format!("{} ({})", task.name, completeness_display)
884 })
885 .collect::<Vec<_>>()
886 .join("\n")
887 }
888}
889
890/// Replaces newlines and other whitespace runs with single spaces and trims.
891///
892/// Useful when pasting multi-line titles into `kasl task` prompts:
893/// ```text
894/// PROJ-42
895/// Fix login redirect
896/// ```
897/// becomes `PROJ-42 Fix login redirect`.
898pub fn collapse_whitespace(s: &str) -> String {
899 s.split_whitespace().collect::<Vec<_>>().join(" ")
900}
901
902/// Normalizes a task/commit name for near-duplicate and ignore-list comparison.
903///
904/// Trims whitespace, collapses internal spaces, lowercases, and strips
905/// trailing punctuation so variants like `"New commit"`, `"New commit."`,
906/// and `" New commit"` map to the same key.
907pub fn normalize_task_name(name: &str) -> String {
908 let mut s = collapse_whitespace(name).to_lowercase();
909
910 loop {
911 let trimmed = s.trim_end_matches(['.', ',', ';', '!', '?', ':', '…']).trim_end();
912 if trimmed.len() == s.len() {
913 break;
914 }
915 s = trimmed.to_string();
916 }
917
918 s
919}
920
921/// Returns true when `name` matches an ignore pattern exactly or by prefix
922/// (after normalization). Used for task discovery filtering.
923pub fn is_ignored_name(name: &str, ignore_names: &[String]) -> bool {
924 let n = normalize_task_name(name);
925 ignore_names.iter().any(|pat| {
926 let p = normalize_task_name(pat);
927 !p.is_empty() && (n == p || n.starts_with(&p))
928 })
929}