lsh-framework 1.1.0 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +70 -4
- package/dist/cli.js +104 -486
- package/dist/commands/doctor.js +427 -0
- package/dist/commands/init.js +371 -0
- package/dist/constants/api.js +94 -0
- package/dist/constants/commands.js +64 -0
- package/dist/constants/config.js +56 -0
- package/dist/constants/database.js +21 -0
- package/dist/constants/errors.js +79 -0
- package/dist/constants/index.js +28 -0
- package/dist/constants/paths.js +28 -0
- package/dist/constants/ui.js +73 -0
- package/dist/constants/validation.js +124 -0
- package/dist/daemon/lshd.js +11 -32
- package/dist/lib/daemon-client-helper.js +7 -4
- package/dist/lib/daemon-client.js +9 -2
- package/dist/lib/format-utils.js +163 -0
- package/dist/lib/job-manager.js +2 -1
- package/dist/lib/platform-utils.js +211 -0
- package/dist/lib/secrets-manager.js +11 -1
- package/dist/lib/string-utils.js +128 -0
- package/dist/services/daemon/daemon-registrar.js +3 -2
- package/dist/services/secrets/secrets.js +154 -30
- package/package.json +10 -74
- package/dist/app.js +0 -33
- package/dist/cicd/analytics.js +0 -261
- package/dist/cicd/auth.js +0 -269
- package/dist/cicd/cache-manager.js +0 -172
- package/dist/cicd/data-retention.js +0 -305
- package/dist/cicd/performance-monitor.js +0 -224
- package/dist/cicd/webhook-receiver.js +0 -640
- package/dist/commands/api.js +0 -346
- package/dist/commands/theme.js +0 -261
- package/dist/commands/zsh-import.js +0 -240
- package/dist/components/App.js +0 -1
- package/dist/components/Divider.js +0 -29
- package/dist/components/REPL.js +0 -43
- package/dist/components/Terminal.js +0 -232
- package/dist/components/UserInput.js +0 -30
- package/dist/daemon/api-server.js +0 -316
- package/dist/daemon/monitoring-api.js +0 -220
- package/dist/lib/api-error-handler.js +0 -185
- package/dist/lib/associative-arrays.js +0 -285
- package/dist/lib/base-api-server.js +0 -290
- package/dist/lib/brace-expansion.js +0 -160
- package/dist/lib/builtin-commands.js +0 -439
- package/dist/lib/executors/builtin-executor.js +0 -52
- package/dist/lib/extended-globbing.js +0 -411
- package/dist/lib/extended-parameter-expansion.js +0 -227
- package/dist/lib/interactive-shell.js +0 -460
- package/dist/lib/job-builtins.js +0 -582
- package/dist/lib/pathname-expansion.js +0 -216
- package/dist/lib/script-runner.js +0 -226
- package/dist/lib/shell-executor.js +0 -2504
- package/dist/lib/shell-parser.js +0 -958
- package/dist/lib/shell-types.js +0 -6
- package/dist/lib/shell.lib.js +0 -40
- package/dist/lib/theme-manager.js +0 -476
- package/dist/lib/variable-expansion.js +0 -385
- package/dist/lib/zsh-compatibility.js +0 -659
- package/dist/lib/zsh-import-manager.js +0 -707
- package/dist/lib/zsh-options.js +0 -328
- package/dist/pipeline/job-tracker.js +0 -491
- package/dist/pipeline/mcli-bridge.js +0 -309
- package/dist/pipeline/pipeline-service.js +0 -1119
- package/dist/pipeline/workflow-engine.js +0 -870
- package/dist/services/api/api.js +0 -58
- package/dist/services/api/auth.js +0 -35
- package/dist/services/api/config.js +0 -7
- package/dist/services/api/file.js +0 -22
- package/dist/services/shell/shell.js +0 -28
- package/dist/services/zapier.js +0 -16
- package/dist/simple-api-server.js +0 -148
|
@@ -1,411 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Extended Globbing Implementation
|
|
3
|
-
* Provides ZSH-compatible extended globbing patterns
|
|
4
|
-
*/
|
|
5
|
-
import * as fs from 'fs';
|
|
6
|
-
import * as path from 'path';
|
|
7
|
-
export class ExtendedGlobber {
|
|
8
|
-
cwd;
|
|
9
|
-
constructor(cwd = process.cwd()) {
|
|
10
|
-
this.cwd = cwd;
|
|
11
|
-
}
|
|
12
|
-
/**
|
|
13
|
-
* Expand extended glob patterns
|
|
14
|
-
*/
|
|
15
|
-
async expandPattern(pattern, options = {}) {
|
|
16
|
-
const opts = {
|
|
17
|
-
cwd: this.cwd,
|
|
18
|
-
includeHidden: false,
|
|
19
|
-
followSymlinks: false,
|
|
20
|
-
extendedGlob: true,
|
|
21
|
-
...options,
|
|
22
|
-
};
|
|
23
|
-
// Handle exclusion patterns: *.txt~*backup*
|
|
24
|
-
if (pattern.includes('~')) {
|
|
25
|
-
return this.expandExclusionPattern(pattern, opts);
|
|
26
|
-
}
|
|
27
|
-
// Handle alternation patterns: (foo|bar).txt
|
|
28
|
-
if (pattern.includes('(') && pattern.includes('|') && pattern.includes(')')) {
|
|
29
|
-
return this.expandAlternationPattern(pattern, opts);
|
|
30
|
-
}
|
|
31
|
-
// Handle numeric ranges: <1-10>.txt
|
|
32
|
-
if (pattern.includes('<') && pattern.includes('-') && pattern.includes('>')) {
|
|
33
|
-
return this.expandNumericRange(pattern, opts);
|
|
34
|
-
}
|
|
35
|
-
// Handle qualifiers: *.txt(.L+10)
|
|
36
|
-
if (pattern.includes('(') && pattern.includes('.')) {
|
|
37
|
-
return this.expandQualifiedPattern(pattern, opts);
|
|
38
|
-
}
|
|
39
|
-
// Handle negation patterns: ^*.backup
|
|
40
|
-
if (pattern.startsWith('^')) {
|
|
41
|
-
return this.expandNegationPattern(pattern, opts);
|
|
42
|
-
}
|
|
43
|
-
// Handle recursive patterns: **/*.txt
|
|
44
|
-
if (pattern.includes('**')) {
|
|
45
|
-
return this.expandRecursivePattern(pattern, opts);
|
|
46
|
-
}
|
|
47
|
-
// Fall back to regular globbing
|
|
48
|
-
return this.expandRegularPattern(pattern, opts);
|
|
49
|
-
}
|
|
50
|
-
/**
|
|
51
|
-
* Expand exclusion patterns: *.txt~*backup*
|
|
52
|
-
*/
|
|
53
|
-
async expandExclusionPattern(pattern, options) {
|
|
54
|
-
const [includePattern, excludePattern] = pattern.split('~');
|
|
55
|
-
const includeResults = await this.expandRegularPattern(includePattern, options);
|
|
56
|
-
const excludeResults = await this.expandRegularPattern(excludePattern, options);
|
|
57
|
-
return includeResults.filter(file => !excludeResults.includes(file));
|
|
58
|
-
}
|
|
59
|
-
/**
|
|
60
|
-
* Expand alternation patterns: (foo|bar).txt
|
|
61
|
-
*/
|
|
62
|
-
async expandAlternationPattern(pattern, options) {
|
|
63
|
-
const results = [];
|
|
64
|
-
// Find alternation groups
|
|
65
|
-
const alternationRegex = /\(([^)]+)\)/g;
|
|
66
|
-
let match;
|
|
67
|
-
while ((match = alternationRegex.exec(pattern)) !== null) {
|
|
68
|
-
const alternatives = match[1].split('|');
|
|
69
|
-
const prefix = pattern.substring(0, match.index);
|
|
70
|
-
const suffix = pattern.substring(match.index + match[0].length);
|
|
71
|
-
for (const alt of alternatives) {
|
|
72
|
-
const altPattern = prefix + alt + suffix;
|
|
73
|
-
const altResults = await this.expandPattern(altPattern, options);
|
|
74
|
-
results.push(...altResults);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
return [...new Set(results)]; // Remove duplicates
|
|
78
|
-
}
|
|
79
|
-
/**
|
|
80
|
-
* Expand numeric ranges: <1-10>.txt
|
|
81
|
-
*/
|
|
82
|
-
async expandNumericRange(pattern, options) {
|
|
83
|
-
const results = [];
|
|
84
|
-
const rangeRegex = /<(\d+)-(\d+)>/g;
|
|
85
|
-
let match;
|
|
86
|
-
while ((match = rangeRegex.exec(pattern)) !== null) {
|
|
87
|
-
const start = parseInt(match[1], 10);
|
|
88
|
-
const end = parseInt(match[2], 10);
|
|
89
|
-
for (let i = start; i <= end; i++) {
|
|
90
|
-
const altPattern = pattern.replace(match[0], i.toString());
|
|
91
|
-
const altResults = await this.expandPattern(altPattern, options);
|
|
92
|
-
results.push(...altResults);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
return [...new Set(results)];
|
|
96
|
-
}
|
|
97
|
-
/**
|
|
98
|
-
* Expand patterns with qualifiers: *.txt(.L+10)
|
|
99
|
-
*/
|
|
100
|
-
async expandQualifiedPattern(pattern, options) {
|
|
101
|
-
const qualifierMatch = pattern.match(/^(.+)\(([^)]+)\)$/);
|
|
102
|
-
if (!qualifierMatch)
|
|
103
|
-
return [];
|
|
104
|
-
const [, basePattern, qualifierStr] = qualifierMatch;
|
|
105
|
-
const baseResults = await this.expandRegularPattern(basePattern, options);
|
|
106
|
-
const qualifiers = this.parseQualifiers(qualifierStr);
|
|
107
|
-
return this.filterByQualifiers(baseResults, qualifiers);
|
|
108
|
-
}
|
|
109
|
-
/**
|
|
110
|
-
* Expand negation patterns: ^*.backup
|
|
111
|
-
*/
|
|
112
|
-
async expandNegationPattern(pattern, options) {
|
|
113
|
-
const negatedPattern = pattern.substring(1); // Remove ^
|
|
114
|
-
const allFiles = await this.getAllFiles(options.cwd || this.cwd, options);
|
|
115
|
-
const negatedFiles = await this.expandRegularPattern(negatedPattern, options);
|
|
116
|
-
return allFiles.filter(file => !negatedFiles.includes(file));
|
|
117
|
-
}
|
|
118
|
-
/**
|
|
119
|
-
* Expand recursive patterns: **\/*.txt
|
|
120
|
-
*/
|
|
121
|
-
async expandRecursivePattern(pattern, options) {
|
|
122
|
-
const results = [];
|
|
123
|
-
const searchDir = options.cwd || this.cwd;
|
|
124
|
-
// Convert **/*.txt to recursive search
|
|
125
|
-
const recursivePattern = pattern.replace(/\*\*\//g, '');
|
|
126
|
-
await this.searchRecursively(searchDir, recursivePattern, results, options);
|
|
127
|
-
return results;
|
|
128
|
-
}
|
|
129
|
-
/**
|
|
130
|
-
* Expand regular glob patterns
|
|
131
|
-
*/
|
|
132
|
-
async expandRegularPattern(pattern, options) {
|
|
133
|
-
const results = [];
|
|
134
|
-
const searchDir = options.cwd || this.cwd;
|
|
135
|
-
// Handle tilde expansion
|
|
136
|
-
const expandedPattern = this.expandTilde(pattern);
|
|
137
|
-
// Split pattern into segments
|
|
138
|
-
const segments = expandedPattern.split('/').filter(seg => seg.length > 0);
|
|
139
|
-
if (segments.length === 0) {
|
|
140
|
-
return [searchDir];
|
|
141
|
-
}
|
|
142
|
-
await this.matchSegments(searchDir, segments, results, options);
|
|
143
|
-
return results.sort();
|
|
144
|
-
}
|
|
145
|
-
/**
|
|
146
|
-
* Parse qualifiers from string
|
|
147
|
-
*/
|
|
148
|
-
parseQualifiers(qualifierStr) {
|
|
149
|
-
const qualifiers = [];
|
|
150
|
-
// Parse size qualifiers: L+10, L-5, L=100
|
|
151
|
-
const sizeMatch = qualifierStr.match(/L([+\-=])(\d+)/);
|
|
152
|
-
if (sizeMatch) {
|
|
153
|
-
qualifiers.push({
|
|
154
|
-
type: 'size',
|
|
155
|
-
operator: sizeMatch[1],
|
|
156
|
-
value: sizeMatch[2],
|
|
157
|
-
});
|
|
158
|
-
}
|
|
159
|
-
// Parse time qualifiers: m-1 (modified within 1 day)
|
|
160
|
-
const timeMatch = qualifierStr.match(/m([+\-=])(\d+)/);
|
|
161
|
-
if (timeMatch) {
|
|
162
|
-
qualifiers.push({
|
|
163
|
-
type: 'time',
|
|
164
|
-
operator: timeMatch[1],
|
|
165
|
-
value: timeMatch[2],
|
|
166
|
-
});
|
|
167
|
-
}
|
|
168
|
-
// Parse type qualifiers: f (file), d (directory)
|
|
169
|
-
const typeMatch = qualifierStr.match(/[fd]/);
|
|
170
|
-
if (typeMatch) {
|
|
171
|
-
qualifiers.push({
|
|
172
|
-
type: 'type',
|
|
173
|
-
operator: '=',
|
|
174
|
-
value: typeMatch[0],
|
|
175
|
-
});
|
|
176
|
-
}
|
|
177
|
-
return qualifiers;
|
|
178
|
-
}
|
|
179
|
-
/**
|
|
180
|
-
* Filter files by qualifiers
|
|
181
|
-
*/
|
|
182
|
-
filterByQualifiers(files, qualifiers) {
|
|
183
|
-
return files.filter(file => {
|
|
184
|
-
try {
|
|
185
|
-
const stats = fs.statSync(file);
|
|
186
|
-
for (const qualifier of qualifiers) {
|
|
187
|
-
if (!this.matchesQualifier(file, stats, qualifier)) {
|
|
188
|
-
return false;
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
return true;
|
|
192
|
-
}
|
|
193
|
-
catch {
|
|
194
|
-
return false;
|
|
195
|
-
}
|
|
196
|
-
});
|
|
197
|
-
}
|
|
198
|
-
/**
|
|
199
|
-
* Check if file matches a qualifier
|
|
200
|
-
*/
|
|
201
|
-
matchesQualifier(file, stats, qualifier) {
|
|
202
|
-
switch (qualifier.type) {
|
|
203
|
-
case 'size': {
|
|
204
|
-
const size = stats.size;
|
|
205
|
-
const targetSize = parseInt(qualifier.value, 10);
|
|
206
|
-
switch (qualifier.operator) {
|
|
207
|
-
case '=': return size === targetSize;
|
|
208
|
-
case '+': return size > targetSize;
|
|
209
|
-
case '-': return size < targetSize;
|
|
210
|
-
case '>': return size >= targetSize;
|
|
211
|
-
default: return false;
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
case 'time': {
|
|
215
|
-
const now = Date.now();
|
|
216
|
-
const fileTime = stats.mtime.getTime();
|
|
217
|
-
const daysDiff = (now - fileTime) / (1000 * 60 * 60 * 24);
|
|
218
|
-
const targetDays = parseInt(qualifier.value, 10);
|
|
219
|
-
switch (qualifier.operator) {
|
|
220
|
-
case '=': return Math.abs(daysDiff) <= targetDays;
|
|
221
|
-
case '+': return daysDiff > targetDays;
|
|
222
|
-
case '-': return daysDiff < targetDays;
|
|
223
|
-
case '>': return daysDiff >= targetDays;
|
|
224
|
-
default: return false;
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
case 'type':
|
|
228
|
-
switch (qualifier.value) {
|
|
229
|
-
case 'f': return stats.isFile();
|
|
230
|
-
case 'd': return stats.isDirectory();
|
|
231
|
-
default: return false;
|
|
232
|
-
}
|
|
233
|
-
default:
|
|
234
|
-
return true;
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
/**
|
|
238
|
-
* Search recursively for files
|
|
239
|
-
*/
|
|
240
|
-
async searchRecursively(dir, pattern, results, options) {
|
|
241
|
-
try {
|
|
242
|
-
const entries = await fs.promises.readdir(dir, { withFileTypes: true });
|
|
243
|
-
for (const entry of entries) {
|
|
244
|
-
const fullPath = path.join(dir, entry.name);
|
|
245
|
-
// Skip hidden files unless explicitly included
|
|
246
|
-
if (!options.includeHidden && entry.name.startsWith('.')) {
|
|
247
|
-
continue;
|
|
248
|
-
}
|
|
249
|
-
if (entry.isDirectory()) {
|
|
250
|
-
// Recursively search subdirectories
|
|
251
|
-
await this.searchRecursively(fullPath, pattern, results, options);
|
|
252
|
-
}
|
|
253
|
-
else if (entry.isFile()) {
|
|
254
|
-
// Check if file matches pattern
|
|
255
|
-
if (this.matchesPattern(entry.name, pattern)) {
|
|
256
|
-
results.push(fullPath);
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
catch {
|
|
262
|
-
// Directory doesn't exist or not readable
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
/**
|
|
266
|
-
* Get all files in directory
|
|
267
|
-
*/
|
|
268
|
-
async getAllFiles(dir, options) {
|
|
269
|
-
const files = [];
|
|
270
|
-
try {
|
|
271
|
-
const entries = await fs.promises.readdir(dir, { withFileTypes: true });
|
|
272
|
-
for (const entry of entries) {
|
|
273
|
-
const fullPath = path.join(dir, entry.name);
|
|
274
|
-
if (!options.includeHidden && entry.name.startsWith('.')) {
|
|
275
|
-
continue;
|
|
276
|
-
}
|
|
277
|
-
if (entry.isFile()) {
|
|
278
|
-
files.push(fullPath);
|
|
279
|
-
}
|
|
280
|
-
else if (entry.isDirectory()) {
|
|
281
|
-
const subFiles = await this.getAllFiles(fullPath, options);
|
|
282
|
-
files.push(...subFiles);
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
catch {
|
|
287
|
-
// Directory doesn't exist or not readable
|
|
288
|
-
}
|
|
289
|
-
return files;
|
|
290
|
-
}
|
|
291
|
-
/**
|
|
292
|
-
* Match segments recursively
|
|
293
|
-
*/
|
|
294
|
-
async matchSegments(currentPath, remainingSegments, results, options) {
|
|
295
|
-
if (remainingSegments.length === 0) {
|
|
296
|
-
results.push(currentPath);
|
|
297
|
-
return;
|
|
298
|
-
}
|
|
299
|
-
const [currentSegment, ...restSegments] = remainingSegments;
|
|
300
|
-
try {
|
|
301
|
-
const entries = await fs.promises.readdir(currentPath, { withFileTypes: true });
|
|
302
|
-
for (const entry of entries) {
|
|
303
|
-
if (!options.includeHidden && entry.name.startsWith('.')) {
|
|
304
|
-
continue;
|
|
305
|
-
}
|
|
306
|
-
if (this.matchesPattern(entry.name, currentSegment)) {
|
|
307
|
-
const fullPath = path.join(currentPath, entry.name);
|
|
308
|
-
if (restSegments.length === 0) {
|
|
309
|
-
results.push(fullPath);
|
|
310
|
-
}
|
|
311
|
-
else if (entry.isDirectory()) {
|
|
312
|
-
await this.matchSegments(fullPath, restSegments, results, options);
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
catch {
|
|
318
|
-
// Directory doesn't exist or not readable
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
/**
|
|
322
|
-
* Check if filename matches pattern
|
|
323
|
-
*/
|
|
324
|
-
matchesPattern(filename, pattern) {
|
|
325
|
-
const regex = this.patternToRegex(pattern);
|
|
326
|
-
return regex.test(filename);
|
|
327
|
-
}
|
|
328
|
-
/**
|
|
329
|
-
* Convert glob pattern to regex
|
|
330
|
-
*/
|
|
331
|
-
patternToRegex(pattern) {
|
|
332
|
-
let regexStr = '';
|
|
333
|
-
let i = 0;
|
|
334
|
-
while (i < pattern.length) {
|
|
335
|
-
const char = pattern[i];
|
|
336
|
-
switch (char) {
|
|
337
|
-
case '*':
|
|
338
|
-
regexStr += '.*';
|
|
339
|
-
break;
|
|
340
|
-
case '?':
|
|
341
|
-
regexStr += '.';
|
|
342
|
-
break;
|
|
343
|
-
case '[': {
|
|
344
|
-
const closeIdx = this.findClosingBracket(pattern, i);
|
|
345
|
-
if (closeIdx === -1) {
|
|
346
|
-
regexStr += '\\[';
|
|
347
|
-
}
|
|
348
|
-
else {
|
|
349
|
-
let charClass = pattern.slice(i + 1, closeIdx);
|
|
350
|
-
if (charClass.startsWith('!') || charClass.startsWith('^')) {
|
|
351
|
-
charClass = '^' + charClass.slice(1);
|
|
352
|
-
}
|
|
353
|
-
regexStr += '[' + charClass + ']';
|
|
354
|
-
i = closeIdx;
|
|
355
|
-
}
|
|
356
|
-
break;
|
|
357
|
-
}
|
|
358
|
-
case '\\':
|
|
359
|
-
if (i + 1 < pattern.length) {
|
|
360
|
-
regexStr += '\\' + pattern[i + 1];
|
|
361
|
-
i++;
|
|
362
|
-
}
|
|
363
|
-
else {
|
|
364
|
-
regexStr += '\\\\';
|
|
365
|
-
}
|
|
366
|
-
break;
|
|
367
|
-
default:
|
|
368
|
-
regexStr += this.escapeRegex(char);
|
|
369
|
-
break;
|
|
370
|
-
}
|
|
371
|
-
i++;
|
|
372
|
-
}
|
|
373
|
-
return new RegExp('^' + regexStr + '$');
|
|
374
|
-
}
|
|
375
|
-
/**
|
|
376
|
-
* Find closing bracket
|
|
377
|
-
*/
|
|
378
|
-
findClosingBracket(str, startIdx) {
|
|
379
|
-
let depth = 1;
|
|
380
|
-
for (let i = startIdx + 1; i < str.length; i++) {
|
|
381
|
-
if (str[i] === '[')
|
|
382
|
-
depth++;
|
|
383
|
-
else if (str[i] === ']') {
|
|
384
|
-
depth--;
|
|
385
|
-
if (depth === 0)
|
|
386
|
-
return i;
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
return -1;
|
|
390
|
-
}
|
|
391
|
-
/**
|
|
392
|
-
* Expand tilde
|
|
393
|
-
*/
|
|
394
|
-
expandTilde(pattern) {
|
|
395
|
-
if (pattern.startsWith('~/')) {
|
|
396
|
-
const homeDir = process.env.HOME || '/';
|
|
397
|
-
return path.join(homeDir, pattern.slice(2));
|
|
398
|
-
}
|
|
399
|
-
if (pattern === '~') {
|
|
400
|
-
return process.env.HOME || '/';
|
|
401
|
-
}
|
|
402
|
-
return pattern;
|
|
403
|
-
}
|
|
404
|
-
/**
|
|
405
|
-
* Escape regex special characters
|
|
406
|
-
*/
|
|
407
|
-
escapeRegex(str) {
|
|
408
|
-
return str.replace(/[.+^$()|[\]{}\\]/g, '\\$&');
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
export default ExtendedGlobber;
|
|
@@ -1,227 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Extended Parameter Expansion Implementation
|
|
3
|
-
* Provides ZSH-compatible parameter expansion features
|
|
4
|
-
*/
|
|
5
|
-
export class ExtendedParameterExpander {
|
|
6
|
-
context;
|
|
7
|
-
constructor(context) {
|
|
8
|
-
this.context = context;
|
|
9
|
-
}
|
|
10
|
-
/**
|
|
11
|
-
* Expand extended parameter expressions
|
|
12
|
-
*/
|
|
13
|
-
expandParameter(paramExpr) {
|
|
14
|
-
// Handle ZSH-style parameter expansion patterns
|
|
15
|
-
// Global substitution: ${name:gs/old/new}
|
|
16
|
-
if (paramExpr.includes(':gs/')) {
|
|
17
|
-
return this.handleGlobalSubstitution(paramExpr);
|
|
18
|
-
}
|
|
19
|
-
// Case conversion: ${name:l} or ${name:u}
|
|
20
|
-
if (paramExpr.match(/:[lu]$/)) {
|
|
21
|
-
return this.handleCaseConversion(paramExpr);
|
|
22
|
-
}
|
|
23
|
-
// Array slicing: ${array[2,4]}
|
|
24
|
-
if (paramExpr.includes('[') && paramExpr.includes(',') && paramExpr.includes(']')) {
|
|
25
|
-
return this.handleArraySlicing(paramExpr);
|
|
26
|
-
}
|
|
27
|
-
// Parameter type: ${(t)var}
|
|
28
|
-
if (paramExpr.match(/^\(t\)/)) {
|
|
29
|
-
return this.handleParameterType(paramExpr);
|
|
30
|
-
}
|
|
31
|
-
// Array keys: ${(k)array}
|
|
32
|
-
if (paramExpr.match(/^\(k\)/)) {
|
|
33
|
-
return this.handleArrayKeys(paramExpr);
|
|
34
|
-
}
|
|
35
|
-
// Array values: ${(v)array}
|
|
36
|
-
if (paramExpr.match(/^\(v\)/)) {
|
|
37
|
-
return this.handleArrayValues(paramExpr);
|
|
38
|
-
}
|
|
39
|
-
// Array length: ${#array}
|
|
40
|
-
if (paramExpr.startsWith('#')) {
|
|
41
|
-
return this.handleArrayLength(paramExpr);
|
|
42
|
-
}
|
|
43
|
-
// Default to regular parameter expansion
|
|
44
|
-
return this.getParameterValue(paramExpr);
|
|
45
|
-
}
|
|
46
|
-
/**
|
|
47
|
-
* Handle global substitution: ${name:gs/old/new}
|
|
48
|
-
*/
|
|
49
|
-
handleGlobalSubstitution(paramExpr) {
|
|
50
|
-
const match = paramExpr.match(/^([^:]+):gs\/([^/]+)\/(.*)$/);
|
|
51
|
-
if (!match)
|
|
52
|
-
return this.getParameterValue(paramExpr);
|
|
53
|
-
const [, param, oldPattern, newPattern] = match;
|
|
54
|
-
const value = this.getParameterValue(param);
|
|
55
|
-
// Convert shell pattern to regex
|
|
56
|
-
const regex = this.patternToRegex(oldPattern);
|
|
57
|
-
return value.replace(regex, newPattern);
|
|
58
|
-
}
|
|
59
|
-
/**
|
|
60
|
-
* Handle case conversion: ${name:l} or ${name:u}
|
|
61
|
-
*/
|
|
62
|
-
handleCaseConversion(paramExpr) {
|
|
63
|
-
const match = paramExpr.match(/^([^:]+):([lu])$/);
|
|
64
|
-
if (!match)
|
|
65
|
-
return this.getParameterValue(paramExpr);
|
|
66
|
-
const [, param, conversion] = match;
|
|
67
|
-
const value = this.getParameterValue(param);
|
|
68
|
-
switch (conversion) {
|
|
69
|
-
case 'l': return value.toLowerCase();
|
|
70
|
-
case 'u': return value.toUpperCase();
|
|
71
|
-
default: return value;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
/**
|
|
75
|
-
* Handle array slicing: ${array[2,4]}
|
|
76
|
-
*/
|
|
77
|
-
handleArraySlicing(paramExpr) {
|
|
78
|
-
const match = paramExpr.match(/^([^[\]]+)\[(\d+),(\d+)\]$/);
|
|
79
|
-
if (!match)
|
|
80
|
-
return this.getParameterValue(paramExpr);
|
|
81
|
-
const [, arrayName, startStr, endStr] = match;
|
|
82
|
-
const start = parseInt(startStr, 10);
|
|
83
|
-
const end = parseInt(endStr, 10);
|
|
84
|
-
if (!this.context.arrays.hasArray(arrayName)) {
|
|
85
|
-
return '';
|
|
86
|
-
}
|
|
87
|
-
const slice = this.context.arrays.getSlice(arrayName, start, end);
|
|
88
|
-
return slice.join(' ');
|
|
89
|
-
}
|
|
90
|
-
/**
|
|
91
|
-
* Handle parameter type: ${(t)var}
|
|
92
|
-
*/
|
|
93
|
-
handleParameterType(paramExpr) {
|
|
94
|
-
const match = paramExpr.match(/^\(t\)(.+)$/);
|
|
95
|
-
if (!match)
|
|
96
|
-
return '';
|
|
97
|
-
const param = match[1];
|
|
98
|
-
if (this.context.arrays.hasArray(param)) {
|
|
99
|
-
const type = this.context.arrays.getArrayType(param);
|
|
100
|
-
return type === 'associative' ? 'association' : 'array';
|
|
101
|
-
}
|
|
102
|
-
if (param in this.context.variables || param in this.context.env) {
|
|
103
|
-
return 'scalar';
|
|
104
|
-
}
|
|
105
|
-
return 'unset';
|
|
106
|
-
}
|
|
107
|
-
/**
|
|
108
|
-
* Handle array keys: ${(k)array}
|
|
109
|
-
*/
|
|
110
|
-
handleArrayKeys(paramExpr) {
|
|
111
|
-
const match = paramExpr.match(/^\(k\)(.+)$/);
|
|
112
|
-
if (!match)
|
|
113
|
-
return '';
|
|
114
|
-
const arrayName = match[1];
|
|
115
|
-
if (!this.context.arrays.hasArray(arrayName)) {
|
|
116
|
-
return '';
|
|
117
|
-
}
|
|
118
|
-
const keys = this.context.arrays.getKeys(arrayName);
|
|
119
|
-
return keys.join(' ');
|
|
120
|
-
}
|
|
121
|
-
/**
|
|
122
|
-
* Handle array values: ${(v)array}
|
|
123
|
-
*/
|
|
124
|
-
handleArrayValues(paramExpr) {
|
|
125
|
-
const match = paramExpr.match(/^\(v\)(.+)$/);
|
|
126
|
-
if (!match)
|
|
127
|
-
return '';
|
|
128
|
-
const arrayName = match[1];
|
|
129
|
-
if (!this.context.arrays.hasArray(arrayName)) {
|
|
130
|
-
return '';
|
|
131
|
-
}
|
|
132
|
-
const values = this.context.arrays.getValues(arrayName);
|
|
133
|
-
return values.join(' ');
|
|
134
|
-
}
|
|
135
|
-
/**
|
|
136
|
-
* Handle array length: ${#array}
|
|
137
|
-
*/
|
|
138
|
-
handleArrayLength(paramExpr) {
|
|
139
|
-
const arrayName = paramExpr.substring(1); // Remove #
|
|
140
|
-
if (!this.context.arrays.hasArray(arrayName)) {
|
|
141
|
-
return '0';
|
|
142
|
-
}
|
|
143
|
-
return this.context.arrays.getLength(arrayName).toString();
|
|
144
|
-
}
|
|
145
|
-
/**
|
|
146
|
-
* Get parameter value from variables or environment
|
|
147
|
-
*/
|
|
148
|
-
getParameterValue(param) {
|
|
149
|
-
// Check arrays first
|
|
150
|
-
if (this.context.arrays.hasArray(param)) {
|
|
151
|
-
const values = this.context.arrays.getValues(param);
|
|
152
|
-
return values.join(' ');
|
|
153
|
-
}
|
|
154
|
-
// Check regular variables
|
|
155
|
-
if (param in this.context.variables) {
|
|
156
|
-
return this.context.variables[param];
|
|
157
|
-
}
|
|
158
|
-
// Check environment variables
|
|
159
|
-
if (param in this.context.env) {
|
|
160
|
-
return this.context.env[param];
|
|
161
|
-
}
|
|
162
|
-
return '';
|
|
163
|
-
}
|
|
164
|
-
/**
|
|
165
|
-
* Convert shell pattern to regex
|
|
166
|
-
*/
|
|
167
|
-
patternToRegex(pattern) {
|
|
168
|
-
const regex = pattern
|
|
169
|
-
.replace(/\\/g, '\\\\') // Escape backslashes first
|
|
170
|
-
.replace(/\*/g, '.*') // * matches any string
|
|
171
|
-
.replace(/\?/g, '.') // ? matches any single character
|
|
172
|
-
.replace(/\[([^\]]+)\]/g, '[$1]') // [abc] character class
|
|
173
|
-
.replace(/\./g, '\\.') // Escape dots
|
|
174
|
-
.replace(/\+/g, '\\+') // Escape plus signs
|
|
175
|
-
.replace(/\^/g, '\\^') // Escape carets
|
|
176
|
-
.replace(/\$/g, '\\$') // Escape dollar signs
|
|
177
|
-
.replace(/\(/g, '\\(') // Escape parentheses
|
|
178
|
-
.replace(/\)/g, '\\)') // Escape parentheses
|
|
179
|
-
.replace(/\|/g, '\\|') // Escape pipes
|
|
180
|
-
.replace(/\{/g, '\\{') // Escape braces
|
|
181
|
-
.replace(/\}/g, '\\}'); // Escape braces
|
|
182
|
-
return new RegExp(regex, 'g');
|
|
183
|
-
}
|
|
184
|
-
/**
|
|
185
|
-
* Expand complex parameter expressions
|
|
186
|
-
*/
|
|
187
|
-
expandComplexParameter(paramExpr) {
|
|
188
|
-
// Handle nested expansions and complex expressions
|
|
189
|
-
let result = paramExpr;
|
|
190
|
-
// Process from innermost to outermost
|
|
191
|
-
while (result.includes('${')) {
|
|
192
|
-
const start = result.lastIndexOf('${');
|
|
193
|
-
const end = result.indexOf('}', start);
|
|
194
|
-
if (end === -1)
|
|
195
|
-
break;
|
|
196
|
-
const innerExpr = result.substring(start + 2, end);
|
|
197
|
-
const expanded = this.expandParameter(innerExpr);
|
|
198
|
-
result = result.substring(0, start) + expanded + result.substring(end + 1);
|
|
199
|
-
}
|
|
200
|
-
return result;
|
|
201
|
-
}
|
|
202
|
-
/**
|
|
203
|
-
* Check if a parameter expression is an array reference
|
|
204
|
-
*/
|
|
205
|
-
isArrayReference(paramExpr) {
|
|
206
|
-
return (paramExpr.match(/^\([kv]\)/) !== null ||
|
|
207
|
-
paramExpr.startsWith('#') ||
|
|
208
|
-
paramExpr.includes('[') && paramExpr.includes(']'));
|
|
209
|
-
}
|
|
210
|
-
/**
|
|
211
|
-
* Get array reference type
|
|
212
|
-
*/
|
|
213
|
-
getArrayReferenceType(paramExpr) {
|
|
214
|
-
if (paramExpr.match(/^\(k\)/))
|
|
215
|
-
return 'keys';
|
|
216
|
-
if (paramExpr.match(/^\(v\)/))
|
|
217
|
-
return 'values';
|
|
218
|
-
if (paramExpr.startsWith('#'))
|
|
219
|
-
return 'length';
|
|
220
|
-
if (paramExpr.includes('[') && paramExpr.includes(','))
|
|
221
|
-
return 'slice';
|
|
222
|
-
if (paramExpr.includes('[') && paramExpr.includes(']'))
|
|
223
|
-
return 'element';
|
|
224
|
-
return 'none';
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
export default ExtendedParameterExpander;
|