lsh-framework
Version:
A powerful, extensible shell with advanced job management, database persistence, and modern CLI features
161 lines (160 loc) • 5.44 kB
JavaScript
/**
* POSIX Brace Expansion Implementation
* Handles patterns like {a,b,c}, {1..5}, {a..z}, etc.
*/
export class BraceExpander {
maxExpansions;
constructor(options = {}) {
this.maxExpansions = options.maxExpansions || 10000;
}
expandBraces(pattern) {
// If no braces, return as-is
if (!pattern.includes('{') || !pattern.includes('}')) {
return [pattern];
}
try {
const results = this.expandPattern(pattern);
// Safety check to prevent excessive expansions
if (results.length > this.maxExpansions) {
return [pattern]; // Return original if too many expansions
}
return results;
}
catch (_error) {
// If expansion fails, return original pattern
return [pattern];
}
}
expandPattern(pattern) {
// Find the first complete brace expression
const braceMatch = this.findBraceExpression(pattern);
if (!braceMatch) {
return [pattern];
}
const { start, end, content } = braceMatch;
const prefix = pattern.substring(0, start);
const suffix = pattern.substring(end + 1);
// Expand the brace content
const expansions = this.expandBraceContent(content);
// Combine prefix + expansion + suffix
const results = [];
for (const expansion of expansions) {
const combined = prefix + expansion + suffix;
// Recursively expand any remaining braces
const furtherExpanded = this.expandPattern(combined);
results.push(...furtherExpanded);
}
return results;
}
findBraceExpression(pattern) {
let braceCount = 0;
let start = -1;
for (let i = 0; i < pattern.length; i++) {
const char = pattern[i];
if (char === '{') {
if (braceCount === 0) {
start = i;
}
braceCount++;
}
else if (char === '}') {
braceCount--;
if (braceCount === 0 && start !== -1) {
const content = pattern.substring(start + 1, i);
return { start, end: i, content };
}
}
}
return null;
}
expandBraceContent(content) {
// Handle sequence expansion first (e.g., 1..5, a..z)
const sequenceMatch = content.match(/^(.+?)\.\.(.+?)(?:\.\.(.+))?$/);
if (sequenceMatch) {
return this.expandSequence(sequenceMatch[1], sequenceMatch[2], sequenceMatch[3]);
}
// Handle comma-separated list (e.g., a,b,c)
if (content.includes(',')) {
return this.expandCommaList(content);
}
// Not a valid brace expression
return ['{' + content + '}'];
}
expandSequence(start, end, step) {
const results = [];
// Determine if numeric or alphabetic sequence
const startNum = parseInt(start, 10);
const endNum = parseInt(end, 10);
const stepNum = step ? parseInt(step, 10) : 1;
if (!isNaN(startNum) && !isNaN(endNum)) {
// Numeric sequence
if (stepNum <= 0)
return ['{' + start + '..' + end + '}'];
if (startNum <= endNum) {
for (let i = startNum; i <= endNum; i += stepNum) {
results.push(i.toString());
}
}
else {
for (let i = startNum; i >= endNum; i -= stepNum) {
results.push(i.toString());
}
}
}
else if (start.length === 1 && end.length === 1) {
// Alphabetic sequence
const startCode = start.charCodeAt(0);
const endCode = end.charCodeAt(0);
if (startCode <= endCode) {
for (let i = startCode; i <= endCode; i += stepNum) {
results.push(String.fromCharCode(i));
}
}
else {
for (let i = startCode; i >= endCode; i -= stepNum) {
results.push(String.fromCharCode(i));
}
}
}
else {
// Invalid sequence
return ['{' + start + '..' + end + '}'];
}
return results;
}
expandCommaList(content) {
const results = [];
let current = '';
let braceCount = 0;
for (let i = 0; i < content.length; i++) {
const char = content[i];
if (char === '{') {
braceCount++;
current += char;
}
else if (char === '}') {
braceCount--;
current += char;
}
else if (char === ',' && braceCount === 0) {
results.push(current.trim());
current = '';
}
else {
current += char;
}
}
if (current) {
results.push(current.trim());
}
return results;
}
// Utility method for expanding multiple patterns
expandMultiplePatterns(patterns) {
const results = [];
for (const pattern of patterns) {
results.push(...this.expandBraces(pattern));
}
return results;
}
}