diff options
Diffstat (limited to 'internal/askcli')
| -rw-r--r-- | internal/askcli/command_complete_uuids.go | 3 | ||||
| -rw-r--r-- | internal/askcli/command_complete_uuids_test.go | 6 | ||||
| -rw-r--r-- | internal/askcli/command_fish.go | 4 | ||||
| -rw-r--r-- | internal/askcli/command_urgency.go | 3 | ||||
| -rw-r--r-- | internal/askcli/commands_registry.go | 207 | ||||
| -rw-r--r-- | internal/askcli/completion.go | 41 | ||||
| -rw-r--r-- | internal/askcli/dispatch.go | 45 |
7 files changed, 225 insertions, 84 deletions
diff --git a/internal/askcli/command_complete_uuids.go b/internal/askcli/command_complete_uuids.go index 3a2ef9e..32af2a5 100644 --- a/internal/askcli/command_complete_uuids.go +++ b/internal/askcli/command_complete_uuids.go @@ -7,7 +7,8 @@ import ( "io" ) -func (d *Dispatcher) handleCompleteUUIDs(ctx context.Context, stdout, stderr io.Writer) (int, error) { +func (d *Dispatcher) handleCompleteUUIDs(ctx context.Context, args []string, stdout, stderr io.Writer) (int, error) { + _ = args var outBuf bytes.Buffer code, err := d.runner.Run(ctx, []string{"status:pending", "export"}, nil, &outBuf, stderr) if code != 0 { diff --git a/internal/askcli/command_complete_uuids_test.go b/internal/askcli/command_complete_uuids_test.go index 4a68e51..94e5693 100644 --- a/internal/askcli/command_complete_uuids_test.go +++ b/internal/askcli/command_complete_uuids_test.go @@ -32,7 +32,7 @@ func TestHandleCompleteUUIDs_PrintsPendingUUIDs(t *testing.T) { }}) var stdout, stderr bytes.Buffer - code, err := d.handleCompleteUUIDs(context.Background(), &stdout, &stderr) + code, err := d.handleCompleteUUIDs(context.Background(), nil, &stdout, &stderr) if err != nil { t.Fatalf("handleCompleteUUIDs returned error: %v", err) } @@ -66,7 +66,7 @@ func TestHandleCompleteUUIDs_ParseError(t *testing.T) { }}) var stdout, stderr bytes.Buffer - code, err := d.handleCompleteUUIDs(context.Background(), &stdout, &stderr) + code, err := d.handleCompleteUUIDs(context.Background(), nil, &stdout, &stderr) if err != nil { t.Fatalf("handleCompleteUUIDs returned error: %v", err) } @@ -101,7 +101,7 @@ func TestHandleCompleteUUIDs_WarnsOnInvalidAliasCache(t *testing.T) { }}) var stdout, stderr bytes.Buffer - code, err := d.handleCompleteUUIDs(context.Background(), &stdout, &stderr) + code, err := d.handleCompleteUUIDs(context.Background(), nil, &stdout, &stderr) if err != nil { t.Fatalf("handleCompleteUUIDs returned error: %v", err) } diff --git a/internal/askcli/command_fish.go b/internal/askcli/command_fish.go index 3d3e709..f4ca9e0 100644 --- a/internal/askcli/command_fish.go +++ b/internal/askcli/command_fish.go @@ -1,12 +1,14 @@ package askcli import ( + "context" "fmt" "io" "os" ) -func (d *Dispatcher) handleFish(args []string, stdout, stderr io.Writer) (int, error) { +func (d *Dispatcher) handleFish(ctx context.Context, args []string, stdout, stderr io.Writer) (int, error) { + _ = ctx if len(args) != 1 { fmt.Fprintln(stderr, "usage: ask fish") return 1, nil diff --git a/internal/askcli/command_urgency.go b/internal/askcli/command_urgency.go index 9911f42..bc7d4fd 100644 --- a/internal/askcli/command_urgency.go +++ b/internal/askcli/command_urgency.go @@ -8,7 +8,8 @@ import ( "sort" ) -func (d *Dispatcher) handleUrgency(ctx context.Context, stdout, stderr io.Writer) (int, error) { +func (d *Dispatcher) handleUrgency(ctx context.Context, args []string, stdout, stderr io.Writer) (int, error) { + _ = args var outBuf bytes.Buffer code, err := d.runner.Run(ctx, []string{"export"}, nil, &outBuf, stderr) if code != 0 { diff --git a/internal/askcli/commands_registry.go b/internal/askcli/commands_registry.go new file mode 100644 index 0000000..4d8d9ba --- /dev/null +++ b/internal/askcli/commands_registry.go @@ -0,0 +1,207 @@ +package askcli + +import ( + "context" + "io" +) + +type commandHandler func(d *Dispatcher, ctx context.Context, args []string, stdin io.Reader, stdout, stderr io.Writer) (int, error) + +type simpleCommand func(d *Dispatcher, ctx context.Context, args []string, stdout, stderr io.Writer) (int, error) + +func wrapSimpleCommand(handler simpleCommand) commandHandler { + return func(d *Dispatcher, ctx context.Context, args []string, stdin io.Reader, stdout, stderr io.Writer) (int, error) { + return handler(d, ctx, args, stdout, stderr) + } +} + +type commandEntry struct { + name string + description string + handler commandHandler + includeInCompletion bool + singleSelector bool +} + +type commandTable struct { + entries []commandEntry + lookup map[string]int +} + +func newCommandTable(entries []commandEntry) commandTable { + lookup := make(map[string]int, len(entries)) + for i := range entries { + entry := entries[i] + lookup[entry.name] = i + } + return commandTable{entries: entries, lookup: lookup} +} + +func (t commandTable) get(name string) (*commandEntry, bool) { + idx, ok := t.lookup[name] + if !ok { + return nil, false + } + return &t.entries[idx], true +} + +func (t commandTable) rootCompletionEntries() []commandEntry { + var entries []commandEntry + for _, entry := range t.entries { + if entry.includeInCompletion { + entries = append(entries, entry) + } + } + return entries +} + +func (t commandTable) singleSelectorNames() []string { + var names []string + for _, entry := range t.entries { + if entry.singleSelector { + names = append(names, entry.name) + } + } + return names +} + +func (t *commandTable) add(entry commandEntry) { + t.entries = append(t.entries, entry) + t.lookup[entry.name] = len(t.entries) - 1 +} + +var commandRegistry = newCommandTable([]commandEntry{ + { + name: "add", + description: "Create a new task", + handler: wrapSimpleCommand((*Dispatcher).handleAdd), + includeInCompletion: true, + }, + { + name: "list", + description: "List active tasks", + handler: wrapSimpleCommand((*Dispatcher).handleList), + includeInCompletion: true, + }, + { + name: "all", + description: "List all tasks", + handler: wrapSimpleCommand((*Dispatcher).handleAll), + includeInCompletion: true, + }, + { + name: "ready", + description: "List READY tasks", + handler: wrapSimpleCommand((*Dispatcher).handleReady), + includeInCompletion: true, + }, + { + name: "info", + description: "Show task details", + handler: wrapSimpleCommand((*Dispatcher).handleInfo), + includeInCompletion: true, + singleSelector: true, + }, + { + name: "annotate", + description: "Add an annotation", + handler: wrapSimpleCommand((*Dispatcher).handleAnnotate), + includeInCompletion: true, + singleSelector: true, + }, + { + name: "start", + description: "Start a task", + handler: wrapSimpleCommand((*Dispatcher).handleStart), + includeInCompletion: true, + singleSelector: true, + }, + { + name: "stop", + description: "Stop a task", + handler: wrapSimpleCommand((*Dispatcher).handleStop), + includeInCompletion: true, + singleSelector: true, + }, + { + name: "done", + description: "Mark a task complete", + handler: wrapSimpleCommand((*Dispatcher).handleDone), + includeInCompletion: true, + singleSelector: true, + }, + { + name: "priority", + description: "Set priority", + handler: wrapSimpleCommand((*Dispatcher).handlePriority), + includeInCompletion: true, + singleSelector: true, + }, + { + name: "tag", + description: "Add or remove a tag", + handler: wrapSimpleCommand((*Dispatcher).handleTag), + includeInCompletion: true, + singleSelector: true, + }, + { + name: "modify", + description: "Modify task fields", + handler: wrapSimpleCommand((*Dispatcher).handleModify), + includeInCompletion: true, + singleSelector: true, + }, + { + name: "denotate", + description: "Remove an annotation", + handler: wrapSimpleCommand((*Dispatcher).handleDenotate), + includeInCompletion: true, + singleSelector: true, + }, + { + name: "delete", + description: "Delete a task", + handler: (*Dispatcher).handleDelete, + includeInCompletion: true, + singleSelector: true, + }, + { + name: "dep", + description: "Manage dependencies", + handler: wrapSimpleCommand((*Dispatcher).handleDep), + includeInCompletion: true, + }, + { + name: "urgency", + description: "List tasks sorted by urgency", + handler: wrapSimpleCommand((*Dispatcher).handleUrgency), + includeInCompletion: true, + }, +}) + +func init() { + commandRegistry.add(commandEntry{ + name: "fish", + description: "Emit Fish shell completion script", + handler: wrapSimpleCommand((*Dispatcher).handleFish), + includeInCompletion: true, + }) + commandRegistry.add(commandEntry{ + name: "help", + description: "Show help", + handler: func(d *Dispatcher, ctx context.Context, args []string, stdin io.Reader, stdout, stderr io.Writer) (int, error) { + _ = ctx + _ = args + _ = stdin + _ = stderr + return d.help(stdout) + }, + includeInCompletion: true, + }) + commandRegistry.add(commandEntry{ + name: "complete-uuids", + description: "Emit task selector list", + handler: wrapSimpleCommand((*Dispatcher).handleCompleteUUIDs), + includeInCompletion: false, + }) +} diff --git a/internal/askcli/completion.go b/internal/askcli/completion.go index b4d8620..889bbc8 100644 --- a/internal/askcli/completion.go +++ b/internal/askcli/completion.go @@ -9,40 +9,6 @@ type fishCompletionItem struct { description string } -var askSingleSelectorCompletionCommands = []string{ - "info", - "annotate", - "start", - "stop", - "done", - "priority", - "tag", - "modify", - "denotate", - "delete", -} - -var askRootCompletionItems = []fishCompletionItem{ - {name: "add", description: "Create a new task"}, - {name: "list", description: "List active tasks"}, - {name: "all", description: "List all tasks"}, - {name: "ready", description: "List READY tasks"}, - {name: "info", description: "Show task details"}, - {name: "annotate", description: "Add an annotation"}, - {name: "start", description: "Start a task"}, - {name: "stop", description: "Stop a task"}, - {name: "done", description: "Mark a task complete"}, - {name: "priority", description: "Set priority"}, - {name: "tag", description: "Add or remove a tag"}, - {name: "dep", description: "Manage dependencies"}, - {name: "urgency", description: "List tasks sorted by urgency"}, - {name: "modify", description: "Modify task fields"}, - {name: "denotate", description: "Remove an annotation"}, - {name: "delete", description: "Delete a task"}, - {name: "fish", description: "Emit Fish shell completion script"}, - {name: "help", description: "Show help"}, -} - var askDepCompletionItems = []fishCompletionItem{ {name: "add", description: "Add a dependency"}, {name: "rm", description: "Remove a dependency"}, @@ -54,7 +20,7 @@ func fishSingleSelectorCompletionContext(positional []string) bool { return false } - for _, command := range askSingleSelectorCompletionCommands { + for _, command := range commandRegistry.singleSelectorNames() { if positional[0] == command { return true } @@ -99,7 +65,8 @@ func FishCompletionFor(binaryPath string) string { writeFishAddDependencyModifierFunction(&b) b.WriteString("complete -c ask -f\n") b.WriteString("complete -c ask -s j -l json -d 'Emit JSON output'\n") - for _, item := range askRootCompletionItems { + for _, entry := range commandRegistry.rootCompletionEntries() { + item := fishCompletionItem{name: entry.name, description: entry.description} writeFishCompletionLine(&b, "__ask_needs_root_completion", item) } for _, item := range askDepCompletionItems { @@ -182,7 +149,7 @@ func writeFishUUIDContextFunction(b *strings.Builder) { b.WriteString(" end\n") b.WriteString(" switch $positional[1]\n") b.WriteString(" case ") - b.WriteString(strings.Join(askSingleSelectorCompletionCommands, " ")) + b.WriteString(strings.Join(commandRegistry.singleSelectorNames(), " ")) b.WriteString("\n") b.WriteString(" return 0\n") b.WriteString(" case '*'\n") diff --git a/internal/askcli/dispatch.go b/internal/askcli/dispatch.go index 142f7b4..c2c7aab 100644 --- a/internal/askcli/dispatch.go +++ b/internal/askcli/dispatch.go @@ -47,51 +47,14 @@ func (d *Dispatcher) Dispatch(ctx context.Context, args []string, stdin io.Reade d.jsonOutput = jsonOutput if len(args) == 0 { - return d.handleList(ctx, []string{"list"}, stdout, stderr) + args = []string{"list"} } subcommand := args[0] - switch subcommand { - case "info": - return d.handleInfo(ctx, args, stdout, stderr) - case "add": - return d.handleAdd(ctx, args, stdout, stderr) - case "list": - return d.handleList(ctx, args, stdout, stderr) - case "all": - return d.handleAll(ctx, args, stdout, stderr) - case "ready": - return d.handleReady(ctx, args, stdout, stderr) - case "dep": - return d.handleDep(ctx, args, stdout, stderr) - case "urgency": - return d.handleUrgency(ctx, stdout, stderr) - case "annotate": - return d.handleAnnotate(ctx, args, stdout, stderr) - case "start": - return d.handleStart(ctx, args, stdout, stderr) - case "stop": - return d.handleStop(ctx, args, stdout, stderr) - case "done": - return d.handleDone(ctx, args, stdout, stderr) - case "priority": - return d.handlePriority(ctx, args, stdout, stderr) - case "tag": - return d.handleTag(ctx, args, stdout, stderr) - case "modify": - return d.handleModify(ctx, args, stdout, stderr) - case "denotate": - return d.handleDenotate(ctx, args, stdout, stderr) - case "delete": - return d.handleDelete(ctx, args, stdin, stdout, stderr) - case "fish": - return d.handleFish(args, stdout, stderr) - case "help": - return d.help(stdout) - case "complete-uuids": - return d.handleCompleteUUIDs(ctx, stdout, stderr) - default: + entry, ok := commandRegistry.get(subcommand) + if !ok { return d.unknownCommand(stderr, subcommand) } + return entry.handler(d, ctx, args, stdin, stdout, stderr) } func (d *Dispatcher) help(w io.Writer) (int, error) { |
