Files
Martin Pander 703ed981ac Add things
2026-02-10 15:54:08 +01:00

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
}