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