a68d701d14
Issue 1: Fix recurrence calculation for overdue tasks - Use completion date (End) as base for next instance, not original due date - If task due Monday completed Wednesday, next is Wednesday+7d not Monday+7d - Fallback to Due date if End is not set - Update test to verify new behavior Issue 2: Fix description parsing to work without quotes - Add parseAddArgs() to extract description from non-modifier words - Description = all words that don't start with +, -, or contain : - Enables: opal add buy groceries +shop carrots → 'buy groceries carrots' - Validate description is required (error if only modifiers) - Validate recurring tasks require due date Issue 3: Implement flexible command syntax - Add preprocessArgs() to parse arguments before Cobra routing - Detect command position and split filters (left) from modifiers (right) - Rewrite os.Args so Cobra routes correctly - Enable both 'opal 2 done' and 'opal done 2' syntax - Commands without modifiers accept filters on either side - Commands with modifiers enforce [filters] command [modifiers] - Add confirmation for modify without filters (modifies all tasks) All commands updated to use preprocessed ParsedArgs from context. All tests passing (33 tests).
113 lines
2.6 KiB
Go
113 lines
2.6 KiB
Go
package cmd
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
|
|
"git.jnss.me/joakim/opal/internal/engine"
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
var modifyCmd = &cobra.Command{
|
|
Use: "modify [modifier...]",
|
|
Short: "Modify task attributes",
|
|
Long: `Modify task attributes. Filters must come before 'modify', modifiers after.
|
|
|
|
Examples:
|
|
opal 2 modify priority:H
|
|
opal +urgent modify due:tomorrow
|
|
opal project:backend modify priority:L
|
|
opal modify priority:H # Modify ALL pending tasks (with confirmation)`,
|
|
Run: func(cmd *cobra.Command, args []string) {
|
|
parsed := getParsedArgs(cmd)
|
|
if err := modifyTasks(parsed.Filters, parsed.Modifiers); err != nil {
|
|
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
},
|
|
}
|
|
|
|
func modifyTasks(filterArgs, modifierArgs []string) error {
|
|
// Validate we have modifiers
|
|
if len(modifierArgs) == 0 {
|
|
return fmt.Errorf("no modifiers specified (modifiers must come after 'modify')")
|
|
}
|
|
|
|
// Parse filter (or use default if no filters specified)
|
|
var filter *engine.Filter
|
|
var err error
|
|
|
|
if len(filterArgs) == 0 {
|
|
// No filters = modify all pending tasks (with confirmation)
|
|
filter = engine.DefaultFilter()
|
|
} else {
|
|
filter, err = engine.ParseFilter(filterArgs)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to parse filter: %w", err)
|
|
}
|
|
}
|
|
|
|
// Load working set for ID resolution
|
|
ws, err := engine.LoadWorkingSet()
|
|
if err != nil {
|
|
// If no working set exists yet, build one
|
|
ws, err = engine.BuildWorkingSet(filter)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to build working set: %w", err)
|
|
}
|
|
}
|
|
|
|
// Resolve tasks
|
|
var tasks []*engine.Task
|
|
|
|
if len(filter.IDs) > 0 {
|
|
// Resolve display IDs
|
|
for _, id := range filter.IDs {
|
|
task, err := ws.GetTaskByDisplayID(id)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
tasks = append(tasks, task)
|
|
}
|
|
} else {
|
|
// Use filter to get tasks
|
|
tasks, err = engine.GetTasks(filter)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if len(tasks) == 0 {
|
|
return fmt.Errorf("no tasks matched")
|
|
}
|
|
|
|
// Confirm if multiple tasks or no filters specified
|
|
if len(tasks) > 1 || len(filterArgs) == 0 {
|
|
fmt.Printf("About to modify %d task(s). Proceed? (y/N): ", len(tasks))
|
|
var confirm string
|
|
fmt.Scanln(&confirm)
|
|
if confirm != "y" && confirm != "Y" {
|
|
fmt.Println("Cancelled.")
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// Parse and apply modifiers
|
|
mod, err := engine.ParseModifier(modifierArgs)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to parse modifiers: %w", err)
|
|
}
|
|
|
|
modified := 0
|
|
for _, task := range tasks {
|
|
if err := mod.Apply(task); err != nil {
|
|
fmt.Fprintf(os.Stderr, "Warning: failed to modify task %s: %v\n", task.UUID, err)
|
|
} else {
|
|
modified++
|
|
}
|
|
}
|
|
|
|
fmt.Printf("Modified %d task(s).\n", modified)
|
|
return nil
|
|
}
|