173 lines
4.5 KiB
TypeScript
173 lines
4.5 KiB
TypeScript
import { parseDurationMs } from "../../../cli/parse-duration.js";
|
|
import { normalizeQueueDropPolicy, normalizeQueueMode } from "./normalize.js";
|
|
import type { QueueDropPolicy, QueueMode } from "./types.js";
|
|
|
|
function parseQueueDebounce(raw?: string): number | undefined {
|
|
if (!raw) return undefined;
|
|
try {
|
|
const parsed = parseDurationMs(raw.trim(), { defaultUnit: "ms" });
|
|
if (!parsed || parsed < 0) return undefined;
|
|
return Math.round(parsed);
|
|
} catch {
|
|
return undefined;
|
|
}
|
|
}
|
|
|
|
function parseQueueCap(raw?: string): number | undefined {
|
|
if (!raw) return undefined;
|
|
const num = Number(raw);
|
|
if (!Number.isFinite(num)) return undefined;
|
|
const cap = Math.floor(num);
|
|
if (cap < 1) return undefined;
|
|
return cap;
|
|
}
|
|
|
|
function parseQueueDirectiveArgs(raw: string): {
|
|
consumed: number;
|
|
queueMode?: QueueMode;
|
|
queueReset: boolean;
|
|
rawMode?: string;
|
|
debounceMs?: number;
|
|
cap?: number;
|
|
dropPolicy?: QueueDropPolicy;
|
|
rawDebounce?: string;
|
|
rawCap?: string;
|
|
rawDrop?: string;
|
|
hasOptions: boolean;
|
|
} {
|
|
let i = 0;
|
|
const len = raw.length;
|
|
while (i < len && /\s/.test(raw[i])) i += 1;
|
|
if (raw[i] === ":") {
|
|
i += 1;
|
|
while (i < len && /\s/.test(raw[i])) i += 1;
|
|
}
|
|
let consumed = i;
|
|
let queueMode: QueueMode | undefined;
|
|
let queueReset = false;
|
|
let rawMode: string | undefined;
|
|
let debounceMs: number | undefined;
|
|
let cap: number | undefined;
|
|
let dropPolicy: QueueDropPolicy | undefined;
|
|
let rawDebounce: string | undefined;
|
|
let rawCap: string | undefined;
|
|
let rawDrop: string | undefined;
|
|
let hasOptions = false;
|
|
const takeToken = (): string | null => {
|
|
if (i >= len) return null;
|
|
const start = i;
|
|
while (i < len && !/\s/.test(raw[i])) i += 1;
|
|
if (start === i) return null;
|
|
const token = raw.slice(start, i);
|
|
while (i < len && /\s/.test(raw[i])) i += 1;
|
|
return token;
|
|
};
|
|
while (i < len) {
|
|
const token = takeToken();
|
|
if (!token) break;
|
|
const lowered = token.trim().toLowerCase();
|
|
if (lowered === "default" || lowered === "reset" || lowered === "clear") {
|
|
queueReset = true;
|
|
consumed = i;
|
|
break;
|
|
}
|
|
if (lowered.startsWith("debounce:") || lowered.startsWith("debounce=")) {
|
|
rawDebounce = token.split(/[:=]/)[1] ?? "";
|
|
debounceMs = parseQueueDebounce(rawDebounce);
|
|
hasOptions = true;
|
|
consumed = i;
|
|
continue;
|
|
}
|
|
if (lowered.startsWith("cap:") || lowered.startsWith("cap=")) {
|
|
rawCap = token.split(/[:=]/)[1] ?? "";
|
|
cap = parseQueueCap(rawCap);
|
|
hasOptions = true;
|
|
consumed = i;
|
|
continue;
|
|
}
|
|
if (lowered.startsWith("drop:") || lowered.startsWith("drop=")) {
|
|
rawDrop = token.split(/[:=]/)[1] ?? "";
|
|
dropPolicy = normalizeQueueDropPolicy(rawDrop);
|
|
hasOptions = true;
|
|
consumed = i;
|
|
continue;
|
|
}
|
|
const mode = normalizeQueueMode(token);
|
|
if (mode) {
|
|
queueMode = mode;
|
|
rawMode = token;
|
|
consumed = i;
|
|
continue;
|
|
}
|
|
// Stop at first unrecognized token.
|
|
break;
|
|
}
|
|
return {
|
|
consumed,
|
|
queueMode,
|
|
queueReset,
|
|
rawMode,
|
|
debounceMs,
|
|
cap,
|
|
dropPolicy,
|
|
rawDebounce,
|
|
rawCap,
|
|
rawDrop,
|
|
hasOptions,
|
|
};
|
|
}
|
|
|
|
export function extractQueueDirective(body?: string): {
|
|
cleaned: string;
|
|
queueMode?: QueueMode;
|
|
queueReset: boolean;
|
|
rawMode?: string;
|
|
hasDirective: boolean;
|
|
debounceMs?: number;
|
|
cap?: number;
|
|
dropPolicy?: QueueDropPolicy;
|
|
rawDebounce?: string;
|
|
rawCap?: string;
|
|
rawDrop?: string;
|
|
hasOptions: boolean;
|
|
} {
|
|
if (!body) {
|
|
return {
|
|
cleaned: "",
|
|
hasDirective: false,
|
|
queueReset: false,
|
|
hasOptions: false,
|
|
};
|
|
}
|
|
const re = /(?:^|\s)\/queue(?=$|\s|:)/i;
|
|
const match = re.exec(body);
|
|
if (!match) {
|
|
return {
|
|
cleaned: body.trim(),
|
|
hasDirective: false,
|
|
queueReset: false,
|
|
hasOptions: false,
|
|
};
|
|
}
|
|
const start = match.index + match[0].indexOf("/queue");
|
|
const argsStart = start + "/queue".length;
|
|
const args = body.slice(argsStart);
|
|
const parsed = parseQueueDirectiveArgs(args);
|
|
const cleanedRaw = `${body.slice(0, start)} ${body.slice(argsStart + parsed.consumed)}`;
|
|
const cleaned = cleanedRaw.replace(/\s+/g, " ").trim();
|
|
return {
|
|
cleaned,
|
|
queueMode: parsed.queueMode,
|
|
queueReset: parsed.queueReset,
|
|
rawMode: parsed.rawMode,
|
|
debounceMs: parsed.debounceMs,
|
|
cap: parsed.cap,
|
|
dropPolicy: parsed.dropPolicy,
|
|
rawDebounce: parsed.rawDebounce,
|
|
rawCap: parsed.rawCap,
|
|
rawDrop: parsed.rawDrop,
|
|
hasDirective: true,
|
|
hasOptions: parsed.hasOptions,
|
|
};
|
|
}
|