448 lines
12 KiB
Go
448 lines
12 KiB
Go
package cmd
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
|
|
"github.com/spf13/cobra"
|
|
"github.com/steipete/gogcli/internal/outfmt"
|
|
"github.com/steipete/gogcli/internal/ui"
|
|
"google.golang.org/api/tasks/v1"
|
|
)
|
|
|
|
func newTasksListCmd(flags *rootFlags) *cobra.Command {
|
|
var max int64
|
|
var page string
|
|
var showCompleted bool
|
|
var showDeleted bool
|
|
var showHidden bool
|
|
var showAssigned bool
|
|
var dueMin string
|
|
var dueMax string
|
|
var completedMin string
|
|
var completedMax string
|
|
var updatedMin string
|
|
|
|
cmd := &cobra.Command{
|
|
Use: "list <tasklistId>",
|
|
Short: "List tasks in a task list",
|
|
Args: cobra.ExactArgs(1),
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
u := ui.FromContext(cmd.Context())
|
|
account, err := requireAccount(flags)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
tasklistID := strings.TrimSpace(args[0])
|
|
if tasklistID == "" {
|
|
return usage("empty tasklistId")
|
|
}
|
|
|
|
svc, err := newTasksService(cmd.Context(), account)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
call := svc.Tasks.List(tasklistID).
|
|
MaxResults(max).
|
|
PageToken(page).
|
|
ShowCompleted(showCompleted).
|
|
ShowDeleted(showDeleted).
|
|
ShowHidden(showHidden).
|
|
ShowAssigned(showAssigned)
|
|
if strings.TrimSpace(dueMin) != "" {
|
|
call = call.DueMin(strings.TrimSpace(dueMin))
|
|
}
|
|
if strings.TrimSpace(dueMax) != "" {
|
|
call = call.DueMax(strings.TrimSpace(dueMax))
|
|
}
|
|
if strings.TrimSpace(completedMin) != "" {
|
|
call = call.CompletedMin(strings.TrimSpace(completedMin))
|
|
}
|
|
if strings.TrimSpace(completedMax) != "" {
|
|
call = call.CompletedMax(strings.TrimSpace(completedMax))
|
|
}
|
|
if strings.TrimSpace(updatedMin) != "" {
|
|
call = call.UpdatedMin(strings.TrimSpace(updatedMin))
|
|
}
|
|
|
|
resp, err := call.Do()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if outfmt.IsJSON(cmd.Context()) {
|
|
return outfmt.WriteJSON(os.Stdout, map[string]any{
|
|
"tasks": resp.Items,
|
|
"nextPageToken": resp.NextPageToken,
|
|
})
|
|
}
|
|
|
|
if len(resp.Items) == 0 {
|
|
u.Err().Println("No tasks")
|
|
return nil
|
|
}
|
|
|
|
w, flush := tableWriter(cmd.Context())
|
|
defer flush()
|
|
fmt.Fprintln(w, "ID\tTITLE\tSTATUS\tDUE\tUPDATED")
|
|
for _, t := range resp.Items {
|
|
status := strings.TrimSpace(t.Status)
|
|
if status == "" {
|
|
status = "needsAction"
|
|
}
|
|
fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n", t.Id, t.Title, status, strings.TrimSpace(t.Due), strings.TrimSpace(t.Updated))
|
|
}
|
|
printNextPageHint(u, resp.NextPageToken)
|
|
return nil
|
|
},
|
|
}
|
|
|
|
cmd.Flags().Int64Var(&max, "max", 20, "Max results (max allowed: 100)")
|
|
cmd.Flags().StringVar(&page, "page", "", "Page token")
|
|
|
|
cmd.Flags().BoolVar(&showCompleted, "show-completed", true, "Include completed tasks (requires --show-hidden for some clients)")
|
|
cmd.Flags().BoolVar(&showDeleted, "show-deleted", false, "Include deleted tasks")
|
|
cmd.Flags().BoolVar(&showHidden, "show-hidden", false, "Include hidden tasks")
|
|
cmd.Flags().BoolVar(&showAssigned, "show-assigned", false, "Include tasks assigned to current user")
|
|
|
|
cmd.Flags().StringVar(&dueMin, "due-min", "", "Lower bound for due date filter (RFC3339)")
|
|
cmd.Flags().StringVar(&dueMax, "due-max", "", "Upper bound for due date filter (RFC3339)")
|
|
cmd.Flags().StringVar(&completedMin, "completed-min", "", "Lower bound for completion date filter (RFC3339)")
|
|
cmd.Flags().StringVar(&completedMax, "completed-max", "", "Upper bound for completion date filter (RFC3339)")
|
|
cmd.Flags().StringVar(&updatedMin, "updated-min", "", "Lower bound for updated time filter (RFC3339)")
|
|
return cmd
|
|
}
|
|
|
|
func newTasksAddCmd(flags *rootFlags) *cobra.Command {
|
|
var title string
|
|
var notes string
|
|
var due string
|
|
var parent string
|
|
var previous string
|
|
|
|
cmd := &cobra.Command{
|
|
Use: "add <tasklistId>",
|
|
Short: "Create a task in a task list",
|
|
Aliases: []string{"create"},
|
|
Args: cobra.ExactArgs(1),
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
u := ui.FromContext(cmd.Context())
|
|
account, err := requireAccount(flags)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
tasklistID := strings.TrimSpace(args[0])
|
|
if tasklistID == "" {
|
|
return usage("empty tasklistId")
|
|
}
|
|
if strings.TrimSpace(title) == "" {
|
|
return usage("required: --title")
|
|
}
|
|
|
|
svc, err := newTasksService(cmd.Context(), account)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
task := &tasks.Task{
|
|
Title: strings.TrimSpace(title),
|
|
Notes: strings.TrimSpace(notes),
|
|
Due: strings.TrimSpace(due),
|
|
}
|
|
call := svc.Tasks.Insert(tasklistID, task)
|
|
if strings.TrimSpace(parent) != "" {
|
|
call = call.Parent(strings.TrimSpace(parent))
|
|
}
|
|
if strings.TrimSpace(previous) != "" {
|
|
call = call.Previous(strings.TrimSpace(previous))
|
|
}
|
|
|
|
created, err := call.Do()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if outfmt.IsJSON(cmd.Context()) {
|
|
return outfmt.WriteJSON(os.Stdout, map[string]any{"task": created})
|
|
}
|
|
u.Out().Printf("id\t%s", created.Id)
|
|
u.Out().Printf("title\t%s", created.Title)
|
|
if strings.TrimSpace(created.Status) != "" {
|
|
u.Out().Printf("status\t%s", created.Status)
|
|
}
|
|
if strings.TrimSpace(created.Due) != "" {
|
|
u.Out().Printf("due\t%s", created.Due)
|
|
}
|
|
if strings.TrimSpace(created.WebViewLink) != "" {
|
|
u.Out().Printf("link\t%s", created.WebViewLink)
|
|
}
|
|
return nil
|
|
},
|
|
}
|
|
|
|
cmd.Flags().StringVar(&title, "title", "", "Task title (required)")
|
|
cmd.Flags().StringVar(¬es, "notes", "", "Task notes/description")
|
|
cmd.Flags().StringVar(&due, "due", "", "Due date/time (RFC3339)")
|
|
cmd.Flags().StringVar(&parent, "parent", "", "Parent task ID (create as subtask)")
|
|
cmd.Flags().StringVar(&previous, "previous", "", "Previous sibling task ID (controls ordering)")
|
|
return cmd
|
|
}
|
|
|
|
func newTasksUpdateCmd(flags *rootFlags) *cobra.Command {
|
|
var title string
|
|
var notes string
|
|
var due string
|
|
var status string
|
|
|
|
cmd := &cobra.Command{
|
|
Use: "update <tasklistId> <taskId>",
|
|
Short: "Update a task",
|
|
Args: cobra.ExactArgs(2),
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
u := ui.FromContext(cmd.Context())
|
|
account, err := requireAccount(flags)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
tasklistID := strings.TrimSpace(args[0])
|
|
taskID := strings.TrimSpace(args[1])
|
|
if tasklistID == "" {
|
|
return usage("empty tasklistId")
|
|
}
|
|
if taskID == "" {
|
|
return usage("empty taskId")
|
|
}
|
|
|
|
patch := &tasks.Task{}
|
|
changed := false
|
|
if cmd.Flags().Changed("title") {
|
|
patch.Title = strings.TrimSpace(title)
|
|
changed = true
|
|
}
|
|
if cmd.Flags().Changed("notes") {
|
|
patch.Notes = strings.TrimSpace(notes)
|
|
changed = true
|
|
}
|
|
if cmd.Flags().Changed("due") {
|
|
patch.Due = strings.TrimSpace(due)
|
|
changed = true
|
|
}
|
|
if cmd.Flags().Changed("status") {
|
|
patch.Status = strings.TrimSpace(status)
|
|
changed = true
|
|
}
|
|
if !changed {
|
|
return usage("no fields to update (set at least one of: --title, --notes, --due, --status)")
|
|
}
|
|
|
|
if cmd.Flags().Changed("status") && patch.Status != "" && patch.Status != "needsAction" && patch.Status != "completed" {
|
|
return usage("invalid --status (expected needsAction or completed)")
|
|
}
|
|
|
|
svc, err := newTasksService(cmd.Context(), account)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
updated, err := svc.Tasks.Patch(tasklistID, taskID, patch).Do()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if outfmt.IsJSON(cmd.Context()) {
|
|
return outfmt.WriteJSON(os.Stdout, map[string]any{"task": updated})
|
|
}
|
|
u.Out().Printf("id\t%s", updated.Id)
|
|
u.Out().Printf("title\t%s", updated.Title)
|
|
if strings.TrimSpace(updated.Status) != "" {
|
|
u.Out().Printf("status\t%s", updated.Status)
|
|
}
|
|
if strings.TrimSpace(updated.Due) != "" {
|
|
u.Out().Printf("due\t%s", updated.Due)
|
|
}
|
|
if strings.TrimSpace(updated.WebViewLink) != "" {
|
|
u.Out().Printf("link\t%s", updated.WebViewLink)
|
|
}
|
|
return nil
|
|
},
|
|
}
|
|
|
|
cmd.Flags().StringVar(&title, "title", "", "New title (set empty to clear)")
|
|
cmd.Flags().StringVar(¬es, "notes", "", "New notes (set empty to clear)")
|
|
cmd.Flags().StringVar(&due, "due", "", "New due date/time (RFC3339; set empty to clear)")
|
|
cmd.Flags().StringVar(&status, "status", "", "New status: needsAction|completed (set empty to clear)")
|
|
return cmd
|
|
}
|
|
|
|
func newTasksDoneCmd(flags *rootFlags) *cobra.Command {
|
|
cmd := &cobra.Command{
|
|
Use: "done <tasklistId> <taskId>",
|
|
Short: "Mark a task as completed",
|
|
Aliases: []string{"complete"},
|
|
Args: cobra.ExactArgs(2),
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
u := ui.FromContext(cmd.Context())
|
|
account, err := requireAccount(flags)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
tasklistID := strings.TrimSpace(args[0])
|
|
taskID := strings.TrimSpace(args[1])
|
|
if tasklistID == "" {
|
|
return usage("empty tasklistId")
|
|
}
|
|
if taskID == "" {
|
|
return usage("empty taskId")
|
|
}
|
|
|
|
svc, err := newTasksService(cmd.Context(), account)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
updated, err := svc.Tasks.Patch(tasklistID, taskID, &tasks.Task{Status: "completed"}).Do()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if outfmt.IsJSON(cmd.Context()) {
|
|
return outfmt.WriteJSON(os.Stdout, map[string]any{"task": updated})
|
|
}
|
|
u.Out().Printf("id\t%s", updated.Id)
|
|
u.Out().Printf("status\t%s", strings.TrimSpace(updated.Status))
|
|
return nil
|
|
},
|
|
}
|
|
return cmd
|
|
}
|
|
|
|
func newTasksUndoCmd(flags *rootFlags) *cobra.Command {
|
|
cmd := &cobra.Command{
|
|
Use: "undo <tasklistId> <taskId>",
|
|
Short: "Mark a task as not completed",
|
|
Aliases: []string{"uncomplete", "undone"},
|
|
Args: cobra.ExactArgs(2),
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
u := ui.FromContext(cmd.Context())
|
|
account, err := requireAccount(flags)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
tasklistID := strings.TrimSpace(args[0])
|
|
taskID := strings.TrimSpace(args[1])
|
|
if tasklistID == "" {
|
|
return usage("empty tasklistId")
|
|
}
|
|
if taskID == "" {
|
|
return usage("empty taskId")
|
|
}
|
|
|
|
svc, err := newTasksService(cmd.Context(), account)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
updated, err := svc.Tasks.Patch(tasklistID, taskID, &tasks.Task{Status: "needsAction"}).Do()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if outfmt.IsJSON(cmd.Context()) {
|
|
return outfmt.WriteJSON(os.Stdout, map[string]any{"task": updated})
|
|
}
|
|
u.Out().Printf("id\t%s", updated.Id)
|
|
u.Out().Printf("status\t%s", strings.TrimSpace(updated.Status))
|
|
return nil
|
|
},
|
|
}
|
|
return cmd
|
|
}
|
|
|
|
func newTasksDeleteCmd(flags *rootFlags) *cobra.Command {
|
|
cmd := &cobra.Command{
|
|
Use: "delete <tasklistId> <taskId>",
|
|
Short: "Delete a task",
|
|
Aliases: []string{"rm", "del"},
|
|
Args: cobra.ExactArgs(2),
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
u := ui.FromContext(cmd.Context())
|
|
account, err := requireAccount(flags)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
tasklistID := strings.TrimSpace(args[0])
|
|
taskID := strings.TrimSpace(args[1])
|
|
if tasklistID == "" {
|
|
return usage("empty tasklistId")
|
|
}
|
|
if taskID == "" {
|
|
return usage("empty taskId")
|
|
}
|
|
|
|
if confirmErr := confirmDestructive(cmd, flags, fmt.Sprintf("delete task %s from list %s", taskID, tasklistID)); confirmErr != nil {
|
|
return confirmErr
|
|
}
|
|
|
|
svc, err := newTasksService(cmd.Context(), account)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := svc.Tasks.Delete(tasklistID, taskID).Do(); err != nil {
|
|
return err
|
|
}
|
|
if outfmt.IsJSON(cmd.Context()) {
|
|
return outfmt.WriteJSON(os.Stdout, map[string]any{
|
|
"deleted": true,
|
|
"id": taskID,
|
|
})
|
|
}
|
|
u.Out().Printf("deleted\ttrue")
|
|
u.Out().Printf("id\t%s", taskID)
|
|
return nil
|
|
},
|
|
}
|
|
return cmd
|
|
}
|
|
|
|
func newTasksClearCmd(flags *rootFlags) *cobra.Command {
|
|
cmd := &cobra.Command{
|
|
Use: "clear <tasklistId>",
|
|
Short: "Clear completed tasks from a task list",
|
|
Args: cobra.ExactArgs(1),
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
u := ui.FromContext(cmd.Context())
|
|
account, err := requireAccount(flags)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
tasklistID := strings.TrimSpace(args[0])
|
|
if tasklistID == "" {
|
|
return usage("empty tasklistId")
|
|
}
|
|
|
|
if confirmErr := confirmDestructive(cmd, flags, fmt.Sprintf("clear completed tasks from list %s", tasklistID)); confirmErr != nil {
|
|
return confirmErr
|
|
}
|
|
|
|
svc, err := newTasksService(cmd.Context(), account)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := svc.Tasks.Clear(tasklistID).Do(); err != nil {
|
|
return err
|
|
}
|
|
if outfmt.IsJSON(cmd.Context()) {
|
|
return outfmt.WriteJSON(os.Stdout, map[string]any{
|
|
"cleared": true,
|
|
"tasklistId": tasklistID,
|
|
})
|
|
}
|
|
u.Out().Printf("cleared\ttrue")
|
|
u.Out().Printf("tasklistId\t%s", tasklistID)
|
|
return nil
|
|
},
|
|
}
|
|
return cmd
|
|
}
|