feat: add uncomplete command to restore completed tasks to pending
Dedicated command that sets status back to pending and clears End time. Unlike undo, works on any completed task regardless of when it was completed. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -34,7 +34,7 @@ var commandNames = []string{
|
||||
"add", "done", "modify", "delete",
|
||||
"start", "stop", "count", "projects", "tags",
|
||||
"info", "edit", "server", "sync", "reports", "setup",
|
||||
"version", "annotate", "denotate", "undo", "log", "completion",
|
||||
"version", "annotate", "denotate", "undo", "uncomplete", "log", "completion",
|
||||
}
|
||||
|
||||
// Report names (dynamically populated)
|
||||
@@ -241,6 +241,7 @@ func init() {
|
||||
annotateCmd.GroupID = "task"
|
||||
denotateCmd.GroupID = "task"
|
||||
undoCmd.GroupID = "task"
|
||||
uncompleteCmd.GroupID = "task"
|
||||
logCmd.GroupID = "task"
|
||||
|
||||
rootCmd.AddCommand(addCmd)
|
||||
@@ -254,6 +255,7 @@ func init() {
|
||||
rootCmd.AddCommand(annotateCmd)
|
||||
rootCmd.AddCommand(denotateCmd)
|
||||
rootCmd.AddCommand(undoCmd)
|
||||
rootCmd.AddCommand(uncompleteCmd)
|
||||
rootCmd.AddCommand(logCmd)
|
||||
|
||||
// Other commands
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"git.jnss.me/joakim/opal/internal/engine"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var uncompleteCmd = &cobra.Command{
|
||||
Use: "uncomplete [filter...]",
|
||||
Short: "Restore a completed task to pending",
|
||||
Long: `Restore a completed task back to pending status.
|
||||
|
||||
Unlike undo, this is a targeted action that works on any completed task
|
||||
regardless of when it was completed.
|
||||
|
||||
Examples:
|
||||
opal 2 uncomplete
|
||||
opal uncomplete +errand`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
parsed := getParsedArgs(cmd)
|
||||
if err := uncompleteTasks(parsed.Filters); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func uncompleteTasks(args []string) error {
|
||||
if len(args) == 0 {
|
||||
return fmt.Errorf("no task specified")
|
||||
}
|
||||
|
||||
filter, err := engine.ParseFilter(args)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse filter: %w", err)
|
||||
}
|
||||
|
||||
ws, err := engine.LoadWorkingSet()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to load working set: %w", err)
|
||||
}
|
||||
|
||||
var tasks []*engine.Task
|
||||
|
||||
if len(filter.IDs) > 0 {
|
||||
for _, id := range filter.IDs {
|
||||
task, err := ws.GetTaskByDisplayID(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tasks = append(tasks, task)
|
||||
}
|
||||
} else {
|
||||
tasks, err = engine.GetTasks(filter)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get tasks: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if len(tasks) == 0 {
|
||||
return fmt.Errorf("no tasks matched filter")
|
||||
}
|
||||
|
||||
uncompleted := 0
|
||||
for _, task := range tasks {
|
||||
if task.Status != engine.StatusCompleted {
|
||||
fmt.Fprintf(os.Stderr, "Warning: task %s is not completed, skipping\n", task.UUID)
|
||||
continue
|
||||
}
|
||||
task.Status = engine.StatusPending
|
||||
task.End = nil
|
||||
if err := task.Save(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Warning: failed to uncomplete task %s: %v\n", task.UUID, err)
|
||||
} else {
|
||||
uncompleted++
|
||||
}
|
||||
}
|
||||
|
||||
if uncompleted == 1 {
|
||||
fmt.Printf("Restored task %s to pending\n", engine.FormatTaskSummary(tasks[0], ws))
|
||||
} else {
|
||||
fmt.Printf("Restored %d task(s) to pending.\n", uncompleted)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user