1// SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
2//
3// SPDX-License-Identifier: AGPL-3.0-or-later
4
5package task
6
7import (
8 "encoding/json"
9 "fmt"
10
11 "git.secluded.site/go-lunatask"
12 "git.secluded.site/lune/internal/client"
13 "git.secluded.site/lune/internal/config"
14 "git.secluded.site/lune/internal/ui"
15 "git.secluded.site/lune/internal/validate"
16 "github.com/spf13/cobra"
17)
18
19// ShowCmd displays a task by ID. Exported for potential use by shortcuts.
20var ShowCmd = &cobra.Command{
21 Use: "show ID",
22 Short: "Show task details",
23 Long: `Show detailed information for a task.
24
25Accepts a UUID or lunatask:// deep link.
26
27Note: Due to end-to-end encryption, task name and notes
28are not available through the API. Only metadata is shown.`,
29 Args: cobra.ExactArgs(1),
30 RunE: runShow,
31}
32
33func init() {
34 ShowCmd.Flags().Bool("json", false, "Output as JSON")
35}
36
37func runShow(cmd *cobra.Command, args []string) error {
38 id, err := validate.Reference(args[0])
39 if err != nil {
40 return err
41 }
42
43 apiClient, err := client.New()
44 if err != nil {
45 return err
46 }
47
48 task, err := ui.Spin("Fetching task…", func() (*lunatask.Task, error) {
49 return apiClient.GetTask(cmd.Context(), id)
50 })
51 if err != nil {
52 return err
53 }
54
55 if mustGetBoolFlag(cmd, "json") {
56 return outputTaskJSON(cmd, task)
57 }
58
59 return printTaskDetails(cmd, task)
60}
61
62func outputTaskJSON(cmd *cobra.Command, task *lunatask.Task) error {
63 enc := json.NewEncoder(cmd.OutOrStdout())
64 enc.SetIndent("", " ")
65
66 if err := enc.Encode(task); err != nil {
67 return fmt.Errorf("encoding JSON: %w", err)
68 }
69
70 return nil
71}
72
73func printTaskDetails(cmd *cobra.Command, task *lunatask.Task) error {
74 link, _ := lunatask.BuildDeepLink(lunatask.ResourceTask, task.ID)
75
76 status := "unknown"
77 if task.Status != nil {
78 status = string(*task.Status)
79 }
80
81 fmt.Fprintf(cmd.OutOrStdout(), "%s\n", ui.H1.Render("Task: "+status))
82 fmt.Fprintf(cmd.OutOrStdout(), " ID: %s\n", task.ID)
83 fmt.Fprintf(cmd.OutOrStdout(), " Link: %s\n", link)
84
85 printAreaGoal(cmd, task)
86
87 if task.ScheduledOn != nil {
88 fmt.Fprintf(cmd.OutOrStdout(), " Scheduled: %s\n", ui.FormatDate(task.ScheduledOn.Time))
89 }
90
91 if task.CompletedAt != nil {
92 fmt.Fprintf(cmd.OutOrStdout(), " Completed: %s\n", ui.FormatDate(*task.CompletedAt))
93 }
94
95 printTaskAttributes(cmd, task)
96
97 fmt.Fprintf(cmd.OutOrStdout(), " Created: %s\n", ui.FormatDate(task.CreatedAt))
98 fmt.Fprintf(cmd.OutOrStdout(), " Updated: %s\n", ui.FormatDate(task.UpdatedAt))
99
100 return nil
101}
102
103func printAreaGoal(cmd *cobra.Command, task *lunatask.Task) {
104 cfg, _ := config.Load()
105
106 if task.AreaID != nil {
107 areaDisplay := *task.AreaID
108 if cfg != nil {
109 if area := cfg.AreaByID(*task.AreaID); area != nil {
110 areaDisplay = fmt.Sprintf("%s (%s)", area.Name, area.Key)
111 }
112 }
113
114 fmt.Fprintf(cmd.OutOrStdout(), " Area: %s\n", areaDisplay)
115 }
116
117 if task.GoalID != nil {
118 goalDisplay := *task.GoalID
119 if cfg != nil {
120 if match := cfg.GoalByID(*task.GoalID); match != nil {
121 goalDisplay = fmt.Sprintf("%s (%s)", match.Goal.Name, match.Goal.Key)
122 }
123 }
124
125 fmt.Fprintf(cmd.OutOrStdout(), " Goal: %s\n", goalDisplay)
126 }
127}
128
129func printTaskAttributes(cmd *cobra.Command, task *lunatask.Task) {
130 if task.Estimate != nil {
131 fmt.Fprintf(cmd.OutOrStdout(), " Estimate: %d min\n", *task.Estimate)
132 }
133
134 if task.Priority != nil && *task.Priority != lunatask.PriorityNormal {
135 fmt.Fprintf(cmd.OutOrStdout(), " Priority: %s\n", task.Priority)
136 }
137
138 if task.Progress != nil {
139 fmt.Fprintf(cmd.OutOrStdout(), " Progress: %d%%\n", *task.Progress)
140 }
141
142 if task.Motivation != nil && *task.Motivation != lunatask.MotivationUnknown {
143 fmt.Fprintf(cmd.OutOrStdout(), " Motivation: %s\n", *task.Motivation)
144 }
145
146 if task.Eisenhower != nil && *task.Eisenhower != lunatask.EisenhowerUncategorized {
147 fmt.Fprintf(cmd.OutOrStdout(), " Eisenhower: %s\n", formatEisenhower(*task.Eisenhower))
148 }
149}
150
151func formatEisenhower(e lunatask.Eisenhower) string {
152 switch e {
153 case lunatask.EisenhowerUncategorized:
154 return "uncategorized"
155 case lunatask.EisenhowerDoNow:
156 return "do now (important + urgent)"
157 case lunatask.EisenhowerDoLater:
158 return "do later (important)"
159 case lunatask.EisenhowerDelegate:
160 return "delegate (urgent)"
161 case lunatask.EisenhowerEliminate:
162 return "eliminate"
163 default:
164 return "unknown"
165 }
166}