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/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}