show.go

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