package cmd import ( "fmt" "os" "strings" "time" "git.jnss.me/joakim/opal/internal/engine" "github.com/google/uuid" "github.com/spf13/cobra" ) var addCmd = &cobra.Command{ Use: "add [modifiers...]", Short: "Add a new task", Long: `Add a new task with optional modifiers. Examples: opal add buy groceries # No quotes needed! opal add review PR priority:H project:backend opal add buy groceries +shop carrots # Tag can be anywhere opal add team meeting due:mon recur:1w +meetings`, Run: func(cmd *cobra.Command, args []string) { parsed := getParsedArgs(cmd) // For add, combine filters and modifiers (all are args to parse) allArgs := append(parsed.Filters, parsed.Modifiers...) if err := addTask(allArgs); err != nil { fmt.Fprintf(os.Stderr, "Error: %v\n", err) os.Exit(1) } }, } func addTask(args []string) error { // Parse description and modifiers from args // Description = all words that are NOT filters/modifiers // Filters/Modifiers = words with +, -, or containing : description, modifierArgs, err := parseAddArgs(args) if err != nil { return err } // Parse modifiers var mod *engine.Modifier if len(modifierArgs) > 0 { mod, err = engine.ParseModifier(modifierArgs) if err != nil { return fmt.Errorf("failed to parse modifiers: %w", err) } } // Check if this is a recurring task isRecurring := mod != nil && mod.SetAttributes["recur"] != nil if isRecurring { // Create recurring task (template + first instance) return addRecurringTask(description, mod) } // Create regular task task, err := engine.CreateTaskWithModifier(description, mod) if err != nil { return fmt.Errorf("failed to create task: %w", err) } fmt.Printf("Created task %s\n", task.UUID) if len(task.Tags) > 0 { fmt.Printf("Tags: %s\n", strings.Join(task.Tags, ", ")) } return nil } // parseAddArgs extracts description and modifiers from args // Description = all non-filter/modifier words joined with spaces // Filters/Modifiers = args with +, -, or containing : func parseAddArgs(args []string) (string, []string, error) { var descParts []string var modifiers []string for _, arg := range args { isFilterOrModifier := strings.HasPrefix(arg, "+") || strings.HasPrefix(arg, "-") || strings.Contains(arg, ":") if isFilterOrModifier { modifiers = append(modifiers, arg) } else { descParts = append(descParts, arg) } } if len(descParts) == 0 { return "", nil, fmt.Errorf("description is required") } description := strings.Join(descParts, " ") return description, modifiers, nil } func addRecurringTask(description string, mod *engine.Modifier) error { // Extract recurrence pattern recurPattern := mod.SetAttributes["recur"] if recurPattern == nil { return fmt.Errorf("no recurrence pattern specified") } // Validate: recurring tasks must have due date if mod.SetAttributes["due"] == nil { return fmt.Errorf("recurring tasks require a due date (use due:YYYY-MM-DD or due:monday)") } duration, err := engine.ParseRecurrencePattern(*recurPattern) if err != nil { return fmt.Errorf("invalid recurrence pattern: %w", err) } // Create template task (without saving yet) now := time.Now() template := &engine.Task{ UUID: uuid.New(), Status: engine.StatusRecurring, Description: description, Priority: engine.PriorityDefault, Created: now, Modified: now, RecurrenceDuration: &duration, Tags: []string{}, } // Create modifier without the recur attribute tempMod := &engine.Modifier{ SetAttributes: make(map[string]*string), AttributeOrder: []string{}, AddTags: mod.AddTags, RemoveTags: mod.RemoveTags, } // Copy all attributes except recur for _, key := range mod.AttributeOrder { if key != "recur" { val := mod.SetAttributes[key] tempMod.SetAttributes[key] = val tempMod.AttributeOrder = append(tempMod.AttributeOrder, key) } } // Apply modifiers to template before first save if err := tempMod.ApplyToNew(template); err != nil { return fmt.Errorf("failed to apply modifiers to template: %w", err) } // Save template if err := template.Save(); err != nil { return fmt.Errorf("failed to save template: %w", err) } // Add tags to template (requires task.ID from save) for _, tag := range mod.AddTags { if err := template.AddTag(tag); err != nil { return fmt.Errorf("failed to add tag to template: %w", err) } } // Create first instance instance := &engine.Task{ UUID: uuid.New(), Status: engine.StatusPending, Description: description, Priority: template.Priority, Created: now, Modified: now, ParentUUID: &template.UUID, Due: template.Due, Wait: template.Wait, Scheduled: template.Scheduled, Project: template.Project, Tags: []string{}, } if err := instance.Save(); err != nil { return fmt.Errorf("failed to save first instance: %w", err) } // Copy tags to instance for _, tag := range template.Tags { if err := instance.AddTag(tag); err != nil { return fmt.Errorf("failed to add tag to instance: %w", err) } } fmt.Printf("Created recurring task %s\n", template.UUID) fmt.Printf("First instance: %s\n", instance.UUID) fmt.Printf("Recurrence: %s\n", engine.FormatRecurrenceDuration(duration)) return nil }