From 818419b4c486a143d201e7c5ba8531d8f99abc00 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Wed, 18 Feb 2026 17:13:40 +0000 Subject: [PATCH] refactor(auto-reply): share set/unset command action parsing --- .../reply/commands-setunset.test.ts | 50 +++++++++++++++++++ src/auto-reply/reply/commands-setunset.ts | 24 +++++++++ src/auto-reply/reply/config-commands.ts | 23 +++++---- src/auto-reply/reply/debug-commands.ts | 23 +++++---- 4 files changed, 98 insertions(+), 22 deletions(-) create mode 100644 src/auto-reply/reply/commands-setunset.test.ts diff --git a/src/auto-reply/reply/commands-setunset.test.ts b/src/auto-reply/reply/commands-setunset.test.ts new file mode 100644 index 0000000000..c40490e491 --- /dev/null +++ b/src/auto-reply/reply/commands-setunset.test.ts @@ -0,0 +1,50 @@ +import { describe, expect, it } from "vitest"; +import { parseSetUnsetCommand, parseSetUnsetCommandAction } from "./commands-setunset.js"; + +describe("parseSetUnsetCommand", () => { + it("parses unset values", () => { + expect( + parseSetUnsetCommand({ + slash: "/config", + action: "unset", + args: "foo.bar", + }), + ).toEqual({ kind: "unset", path: "foo.bar" }); + }); + + it("parses set values", () => { + expect( + parseSetUnsetCommand({ + slash: "/config", + action: "set", + args: 'foo.bar={"x":1}', + }), + ).toEqual({ kind: "set", path: "foo.bar", value: { x: 1 } }); + }); +}); + +describe("parseSetUnsetCommandAction", () => { + it("returns null for non set/unset actions", () => { + const result = parseSetUnsetCommandAction({ + slash: "/config", + action: "show", + args: "", + onSet: (path, value) => ({ action: "set", path, value }), + onUnset: (path) => ({ action: "unset", path }), + onError: (message) => ({ action: "error", message }), + }); + expect(result).toBeNull(); + }); + + it("maps parse errors through onError", () => { + const result = parseSetUnsetCommandAction({ + slash: "/config", + action: "set", + args: "", + onSet: (path, value) => ({ action: "set", path, value }), + onUnset: (path) => ({ action: "unset", path }), + onError: (message) => ({ action: "error", message }), + }); + expect(result).toEqual({ action: "error", message: "Usage: /config set path=value" }); + }); +}); diff --git a/src/auto-reply/reply/commands-setunset.ts b/src/auto-reply/reply/commands-setunset.ts index 137973a5e6..ee0f3fe797 100644 --- a/src/auto-reply/reply/commands-setunset.ts +++ b/src/auto-reply/reply/commands-setunset.ts @@ -36,3 +36,27 @@ export function parseSetUnsetCommand(params: { } return { kind: "set", path, value: parsed.value }; } + +export function parseSetUnsetCommandAction(params: { + slash: string; + action: string; + args: string; + onSet: (path: string, value: unknown) => T; + onUnset: (path: string) => T; + onError: (message: string) => T; +}): T | null { + if (params.action !== "set" && params.action !== "unset") { + return null; + } + const parsed = parseSetUnsetCommand({ + slash: params.slash, + action: params.action, + args: params.args, + }); + if (parsed.kind === "error") { + return params.onError(parsed.message); + } + return parsed.kind === "set" + ? params.onSet(parsed.path, parsed.value) + : params.onUnset(parsed.path); +} diff --git a/src/auto-reply/reply/config-commands.ts b/src/auto-reply/reply/config-commands.ts index fc924985c5..b864f876ab 100644 --- a/src/auto-reply/reply/config-commands.ts +++ b/src/auto-reply/reply/config-commands.ts @@ -1,4 +1,4 @@ -import { parseSetUnsetCommand } from "./commands-setunset.js"; +import { parseSetUnsetCommandAction } from "./commands-setunset.js"; import { parseSlashCommandOrNull } from "./commands-slash-parse.js"; export type ConfigCommand = @@ -18,22 +18,23 @@ export function parseConfigCommand(raw: string): ConfigCommand | null { return { action: "error", message: parsed.message }; } const { action, args } = parsed; + const setUnset = parseSetUnsetCommandAction({ + slash: "/config", + action, + args, + onSet: (path, value) => ({ action: "set", path, value }), + onUnset: (path) => ({ action: "unset", path }), + onError: (message) => ({ action: "error", message }), + }); + if (setUnset) { + return setUnset; + } switch (action) { case "show": return { action: "show", path: args || undefined }; case "get": return { action: "show", path: args || undefined }; - case "unset": - case "set": { - const parsed = parseSetUnsetCommand({ slash: "/config", action, args }); - if (parsed.kind === "error") { - return { action: "error", message: parsed.message }; - } - return parsed.kind === "set" - ? { action: "set", path: parsed.path, value: parsed.value } - : { action: "unset", path: parsed.path }; - } default: return { action: "error", diff --git a/src/auto-reply/reply/debug-commands.ts b/src/auto-reply/reply/debug-commands.ts index 089caf2a5e..89df54201a 100644 --- a/src/auto-reply/reply/debug-commands.ts +++ b/src/auto-reply/reply/debug-commands.ts @@ -1,4 +1,4 @@ -import { parseSetUnsetCommand } from "./commands-setunset.js"; +import { parseSetUnsetCommandAction } from "./commands-setunset.js"; import { parseSlashCommandOrNull } from "./commands-slash-parse.js"; export type DebugCommand = @@ -19,22 +19,23 @@ export function parseDebugCommand(raw: string): DebugCommand | null { return { action: "error", message: parsed.message }; } const { action, args } = parsed; + const setUnset = parseSetUnsetCommandAction({ + slash: "/debug", + action, + args, + onSet: (path, value) => ({ action: "set", path, value }), + onUnset: (path) => ({ action: "unset", path }), + onError: (message) => ({ action: "error", message }), + }); + if (setUnset) { + return setUnset; + } switch (action) { case "show": return { action: "show" }; case "reset": return { action: "reset" }; - case "unset": - case "set": { - const parsed = parseSetUnsetCommand({ slash: "/debug", action, args }); - if (parsed.kind === "error") { - return { action: "error", message: parsed.message }; - } - return parsed.kind === "set" - ? { action: "set", path: parsed.path, value: parsed.value } - : { action: "unset", path: parsed.path }; - } default: return { action: "error",