import type { Plugin, PluginMatch, TemplateContext, } from "../../types/index.js"; import { PluginPriority, SKIP_PLUGIN } from "../../types/index.js"; import { getNestedValue } from "../../utils/nested-value.js"; /** * Regex transformation plugin. * * Syntax: * - `{text:/pattern/flags}` — return first match * - `{text:/pattern/flags:replace:replacement}` — replace all matches * - `{text:/pattern/flags:0}` — return capture group 0 (full match) * - `{text:/pattern/flags:1}` — return capture group 1 * - `{phone:/\\D/g:replace:}` — remove all non-digits * * Variables starting with `_` are skipped. */ export function createRegexPlugin< TContext extends TemplateContext = TemplateContext, >(): Plugin { return { name: "regex", priority: PluginPriority.MEDIUM, pattern: /\{([a-zA-Z_$][\w.$]*):\/([^/]+)\/([gimsuy]*)?(?::(\w+)(?::([^}]*))?)?\}/g, transform( match: PluginMatch, context: TContext ): string | typeof SKIP_PLUGIN { const path = match.groups?.[1]; const pattern = match.groups?.[2]; const flags = match.groups?.[3] ?? ""; const operation = match.groups?.[4]; const param = match.groups?.[5]; if (!path || !pattern) return SKIP_PLUGIN; if (path.startsWith("_")) return SKIP_PLUGIN; const value = getNestedValue(context, path); if (value === undefined || value === null) return ""; const str = String(value); try { const regex = new RegExp(pattern, flags); if (operation === "replace") { return str.replace(regex, param ?? ""); } if (operation !== undefined && /^\d+$/.test(operation)) { const groupIndex = Number.parseInt(operation, 10); const matches = str.match(regex); if (!matches || groupIndex >= matches.length) return ""; return matches[groupIndex] ?? ""; } // Default: return first match return str.match(regex)?.[0] ?? ""; } catch { return match.match; } }, }; }