128 lines
3.0 KiB
Go
128 lines
3.0 KiB
Go
package taskwarrior
|
|
|
|
import (
|
|
"log/slog"
|
|
)
|
|
|
|
// TaskNode represents a task in the tree structure
|
|
type TaskNode struct {
|
|
Task *Task
|
|
Children []*TaskNode
|
|
Parent *TaskNode
|
|
Depth int
|
|
}
|
|
|
|
// TaskTree manages the hierarchical task structure
|
|
type TaskTree struct {
|
|
Nodes map[string]*TaskNode // UUID -> TaskNode
|
|
Roots []*TaskNode // Top-level tasks (no parent)
|
|
FlatList []*TaskNode // Flattened tree in display order
|
|
}
|
|
|
|
// BuildTaskTree constructs a tree from a flat list of tasks
|
|
// Three-pass algorithm:
|
|
// 1. Create all nodes
|
|
// 2. Establish parent-child relationships
|
|
// 3. Calculate depths and flatten tree
|
|
func BuildTaskTree(tasks Tasks) *TaskTree {
|
|
tree := &TaskTree{
|
|
Nodes: make(map[string]*TaskNode),
|
|
Roots: make([]*TaskNode, 0),
|
|
FlatList: make([]*TaskNode, 0),
|
|
}
|
|
|
|
// Pass 1: Create all nodes
|
|
for _, task := range tasks {
|
|
node := &TaskNode{
|
|
Task: task,
|
|
Children: make([]*TaskNode, 0),
|
|
Parent: nil,
|
|
Depth: 0,
|
|
}
|
|
tree.Nodes[task.Uuid] = node
|
|
}
|
|
|
|
// Pass 2: Establish parent-child relationships
|
|
// Iterate over original tasks slice to preserve order
|
|
for _, task := range tasks {
|
|
node := tree.Nodes[task.Uuid]
|
|
parentUUID := getParentUUID(node.Task)
|
|
if parentUUID == "" {
|
|
// No parent, this is a root task
|
|
tree.Roots = append(tree.Roots, node)
|
|
} else {
|
|
// Find parent node
|
|
parentNode, exists := tree.Nodes[parentUUID]
|
|
if !exists {
|
|
// Orphaned task - missing parent
|
|
slog.Warn("Task has missing parent",
|
|
"task_uuid", node.Task.Uuid,
|
|
"parent_uuid", parentUUID,
|
|
"task_desc", node.Task.Description)
|
|
// Treat as root (graceful degradation)
|
|
tree.Roots = append(tree.Roots, node)
|
|
} else {
|
|
// Establish relationship
|
|
node.Parent = parentNode
|
|
parentNode.Children = append(parentNode.Children, node)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Pass 3: Calculate depths and flatten tree
|
|
for _, root := range tree.Roots {
|
|
flattenNode(root, 0, &tree.FlatList)
|
|
}
|
|
|
|
return tree
|
|
}
|
|
|
|
// getParentUUID extracts the parent UUID from a task's UDAs
|
|
func getParentUUID(task *Task) string {
|
|
if task.Udas == nil {
|
|
return ""
|
|
}
|
|
|
|
parentVal, exists := task.Udas["parenttask"]
|
|
if !exists {
|
|
return ""
|
|
}
|
|
|
|
// Parent UDA is stored as a string
|
|
if parentStr, ok := parentVal.(string); ok {
|
|
return parentStr
|
|
}
|
|
|
|
return ""
|
|
}
|
|
|
|
// flattenNode recursively flattens the tree in depth-first order
|
|
func flattenNode(node *TaskNode, depth int, flatList *[]*TaskNode) {
|
|
node.Depth = depth
|
|
*flatList = append(*flatList, node)
|
|
|
|
// Recursively flatten children
|
|
for _, child := range node.Children {
|
|
flattenNode(child, depth+1, flatList)
|
|
}
|
|
}
|
|
|
|
// GetChildrenStatus returns completed/total counts for a parent task
|
|
func (tn *TaskNode) GetChildrenStatus() (completed int, total int) {
|
|
total = len(tn.Children)
|
|
completed = 0
|
|
|
|
for _, child := range tn.Children {
|
|
if child.Task.Status == "completed" {
|
|
completed++
|
|
}
|
|
}
|
|
|
|
return completed, total
|
|
}
|
|
|
|
// HasChildren returns true if the node has any children
|
|
func (tn *TaskNode) HasChildren() bool {
|
|
return len(tn.Children) > 0
|
|
}
|