refactor(auto-reply): share slash parsing for config/debug

This commit is contained in:
Peter Steinberger
2026-02-16 02:10:57 +00:00
parent 1b6704ef53
commit d9ca051a1d
4 changed files with 116 additions and 88 deletions

View File

@@ -0,0 +1,38 @@
import { parseConfigValue } from "./config-value.js";
export type SetUnsetParseResult =
| { kind: "set"; path: string; value: unknown }
| { kind: "unset"; path: string }
| { kind: "error"; message: string };
export function parseSetUnsetCommand(params: {
slash: string;
action: "set" | "unset";
args: string;
}): SetUnsetParseResult {
const action = params.action;
const args = params.args.trim();
if (action === "unset") {
if (!args) {
return { kind: "error", message: `Usage: ${params.slash} unset path` };
}
return { kind: "unset", path: args };
}
if (!args) {
return { kind: "error", message: `Usage: ${params.slash} set path=value` };
}
const eqIndex = args.indexOf("=");
if (eqIndex <= 0) {
return { kind: "error", message: `Usage: ${params.slash} set path=value` };
}
const path = args.slice(0, eqIndex).trim();
const rawValue = args.slice(eqIndex + 1);
if (!path) {
return { kind: "error", message: `Usage: ${params.slash} set path=value` };
}
const parsed = parseConfigValue(rawValue);
if (parsed.error) {
return { kind: "error", message: parsed.error };
}
return { kind: "set", path, value: parsed.value };
}

View File

@@ -0,0 +1,46 @@
export type SlashCommandParseResult =
| { kind: "no-match" }
| { kind: "empty" }
| { kind: "invalid" }
| { kind: "parsed"; action: string; args: string };
export type ParsedSlashCommand =
| { ok: true; action: string; args: string }
| { ok: false; message: string };
export function parseSlashCommandActionArgs(raw: string, slash: string): SlashCommandParseResult {
const trimmed = raw.trim();
const slashLower = slash.toLowerCase();
if (!trimmed.toLowerCase().startsWith(slashLower)) {
return { kind: "no-match" };
}
const rest = trimmed.slice(slash.length).trim();
if (!rest) {
return { kind: "empty" };
}
const match = rest.match(/^(\S+)(?:\s+([\s\S]+))?$/);
if (!match) {
return { kind: "invalid" };
}
const action = match[1]?.toLowerCase() ?? "";
const args = (match[2] ?? "").trim();
return { kind: "parsed", action, args };
}
export function parseSlashCommandOrNull(
raw: string,
slash: string,
opts: { invalidMessage: string; defaultAction?: string },
): ParsedSlashCommand | null {
const parsed = parseSlashCommandActionArgs(raw, slash);
if (parsed.kind === "no-match") {
return null;
}
if (parsed.kind === "invalid") {
return { ok: false, message: opts.invalidMessage };
}
if (parsed.kind === "empty") {
return { ok: true, action: opts.defaultAction ?? "show", args: "" };
}
return { ok: true, action: parsed.action, args: parsed.args };
}

View File

@@ -1,4 +1,5 @@
import { parseConfigValue } from "./config-value.js";
import { parseSetUnsetCommand } from "./commands-setunset.js";
import { parseSlashCommandOrNull } from "./commands-slash-parse.js";
export type ConfigCommand =
| { action: "show"; path?: string }
@@ -7,60 +8,31 @@ export type ConfigCommand =
| { action: "error"; message: string };
export function parseConfigCommand(raw: string): ConfigCommand | null {
const trimmed = raw.trim();
if (!trimmed.toLowerCase().startsWith("/config")) {
const parsed = parseSlashCommandOrNull(raw, "/config", {
invalidMessage: "Invalid /config syntax.",
});
if (!parsed) {
return null;
}
const rest = trimmed.slice("/config".length).trim();
if (!rest) {
return { action: "show" };
if (!parsed.ok) {
return { action: "error", message: parsed.message };
}
const match = rest.match(/^(\S+)(?:\s+([\s\S]+))?$/);
if (!match) {
return { action: "error", message: "Invalid /config syntax." };
}
const action = match[1].toLowerCase();
const args = (match[2] ?? "").trim();
const { action, args } = parsed;
switch (action) {
case "show":
return { action: "show", path: args || undefined };
case "get":
return { action: "show", path: args || undefined };
case "unset": {
if (!args) {
return { action: "error", message: "Usage: /config unset path" };
}
return { action: "unset", path: args };
}
case "unset":
case "set": {
if (!args) {
return {
action: "error",
message: "Usage: /config set path=value",
};
const parsed = parseSetUnsetCommand({ slash: "/config", action, args });
if (parsed.kind === "error") {
return { action: "error", message: parsed.message };
}
const eqIndex = args.indexOf("=");
if (eqIndex <= 0) {
return {
action: "error",
message: "Usage: /config set path=value",
};
}
const path = args.slice(0, eqIndex).trim();
const rawValue = args.slice(eqIndex + 1);
if (!path) {
return {
action: "error",
message: "Usage: /config set path=value",
};
}
const parsed = parseConfigValue(rawValue);
if (parsed.error) {
return { action: "error", message: parsed.error };
}
return { action: "set", path, value: parsed.value };
return parsed.kind === "set"
? { action: "set", path: parsed.path, value: parsed.value }
: { action: "unset", path: parsed.path };
}
default:
return {

View File

@@ -1,4 +1,5 @@
import { parseConfigValue } from "./config-value.js";
import { parseSetUnsetCommand } from "./commands-setunset.js";
import { parseSlashCommandOrNull } from "./commands-slash-parse.js";
export type DebugCommand =
| { action: "show" }
@@ -8,60 +9,31 @@ export type DebugCommand =
| { action: "error"; message: string };
export function parseDebugCommand(raw: string): DebugCommand | null {
const trimmed = raw.trim();
if (!trimmed.toLowerCase().startsWith("/debug")) {
const parsed = parseSlashCommandOrNull(raw, "/debug", {
invalidMessage: "Invalid /debug syntax.",
});
if (!parsed) {
return null;
}
const rest = trimmed.slice("/debug".length).trim();
if (!rest) {
return { action: "show" };
if (!parsed.ok) {
return { action: "error", message: parsed.message };
}
const match = rest.match(/^(\S+)(?:\s+([\s\S]+))?$/);
if (!match) {
return { action: "error", message: "Invalid /debug syntax." };
}
const action = match[1].toLowerCase();
const args = (match[2] ?? "").trim();
const { action, args } = parsed;
switch (action) {
case "show":
return { action: "show" };
case "reset":
return { action: "reset" };
case "unset": {
if (!args) {
return { action: "error", message: "Usage: /debug unset path" };
}
return { action: "unset", path: args };
}
case "unset":
case "set": {
if (!args) {
return {
action: "error",
message: "Usage: /debug set path=value",
};
const parsed = parseSetUnsetCommand({ slash: "/debug", action, args });
if (parsed.kind === "error") {
return { action: "error", message: parsed.message };
}
const eqIndex = args.indexOf("=");
if (eqIndex <= 0) {
return {
action: "error",
message: "Usage: /debug set path=value",
};
}
const path = args.slice(0, eqIndex).trim();
const rawValue = args.slice(eqIndex + 1);
if (!path) {
return {
action: "error",
message: "Usage: /debug set path=value",
};
}
const parsed = parseConfigValue(rawValue);
if (parsed.error) {
return { action: "error", message: parsed.error };
}
return { action: "set", path, value: parsed.value };
return parsed.kind === "set"
? { action: "set", path: parsed.path, value: parsed.value }
: { action: "unset", path: parsed.path };
}
default:
return {