UNPKG

@syntropiq/xtrax

Version:

XTRAX - Serverless-ready TypeScript library for data processing and regex-based parsing applications

116 lines 4.33 kB
/** * Process contents of variables.json, in preparation for recursive substitution: * * - Strip keys ending in '#', which are treated as comments * - Flatten nested dicts, so {"page": {"": "A", "foo": "B"}} becomes {"page": "A", "page_foo": "B"} * - Add optional variants for each key, so {"page": "\\d+"} becomes {"page_optional": "(?:\\d+ ?)?"} * - Resolve nested references safely with circular reference detection */ export function processVariables(variables) { // Flatten variables and remove comments function flatten(d, parentKey = '') { const items = {}; for (const [k, v] of Object.entries(d)) { // Skip comment keys if (k.endsWith('#')) { continue; } const newKey = [parentKey, k].filter(Boolean).join('_'); if (typeof v === 'object' && v !== null && !Array.isArray(v)) { // Recursively flatten nested objects Object.assign(items, flatten(v, newKey)); } else { // Convert to string and store items[newKey] = String(v); } } return items; } let processedVariables = flatten(variables); // Add optional variables - wrap each variable in optional group const optionalVars = {}; for (const [k, v] of Object.entries(processedVariables)) { optionalVars[`${k}_optional`] = `(?:${v} ?)?`; } processedVariables = { ...processedVariables, ...optionalVars }; // Resolve references safely - detect cycles and skip problematic variables const resolvedVariables = {}; for (const [k, v] of Object.entries(processedVariables)) { try { resolvedVariables[k] = recursiveSubstitute(v, processedVariables); } catch (error) { // If we hit max depth (circular reference), just use the original value console.warn(`Circular reference detected for variable '${k}': ${v}`); resolvedVariables[k] = v; } } return resolvedVariables; } /** * Recursively substitute values in `template` from `variables`. For example: * recursiveSubstitute("$a $b $c", {'a': '$b', 'b': '$c', 'c': 'foo'}) * Result: "foo foo foo" * * Infinite loops will be detected after maxDepth iterations and the function * will return the partially resolved result. */ export function recursiveSubstitute(template, variables, maxDepth = 100) { let oldVal = template; for (let i = 0; i < maxDepth; i++) { // Replace variables in the format $var or ${var} const newVal = oldVal.replace(/\$\{?(\w+)\}?/g, (match, varName) => { return variables[varName] || match; }); // If no changes were made, we're done if (newVal === oldVal) { break; } oldVal = newVal; } // Don't throw error for unresolved variables - just return what we have // This matches the Python behavior where unresolved variables are left as-is return oldVal; } /** * Process variables with detailed result information */ export function processVariablesWithResult(variables) { const startTime = Date.now(); const originalCount = countVariables(variables); const processed = processVariables(variables); const endTime = Date.now(); const processedCount = Object.keys(processed).length; return { variables: processed, stats: { originalVariableCount: originalCount, processedVariableCount: processedCount, processingTimeMs: endTime - startTime, optionalVariablesAdded: Math.floor((processedCount - originalCount) / 2) // Rough estimate } }; } /** * Count variables in nested structure */ function countVariables(variables) { let count = 0; function countRecursive(obj) { for (const [key, value] of Object.entries(obj)) { if (key.endsWith('#')) { continue; // Skip comments } if (typeof value === 'object' && value !== null && !Array.isArray(value)) { countRecursive(value); } else { count++; } } } countRecursive(variables); return count; } //# sourceMappingURL=variable-processor.js.map