papagaio 0.2.3 → 0.2.4
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 +9 -24
- package/package.json +1 -1
- package/src/papagaio.js +154 -168
- package/tests/tests.json +18 -6
package/README.md
CHANGED
|
@@ -52,17 +52,6 @@ Output: `hello & world` (both)
|
|
|
52
52
|
|
|
53
53
|
`$$` = zero or more spaces/tabs/newlines.
|
|
54
54
|
|
|
55
|
-
### 4. Literal Output (`$$`)
|
|
56
|
-
|
|
57
|
-
```
|
|
58
|
-
Price: $$50
|
|
59
|
-
```
|
|
60
|
-
Output: `Price: $50`
|
|
61
|
-
|
|
62
|
-
Use `$$` for literal `$` output.
|
|
63
|
-
|
|
64
|
-
---
|
|
65
|
-
|
|
66
55
|
## Blocks
|
|
67
56
|
|
|
68
57
|
Capture content between delimiters.
|
|
@@ -281,6 +270,9 @@ Output:
|
|
|
281
270
|
- Reuse: `$x` appears multiple times in replace
|
|
282
271
|
- Undefined: becomes empty string
|
|
283
272
|
|
|
273
|
+
### Sigil
|
|
274
|
+
- You cannot match words containing the sigil character.
|
|
275
|
+
|
|
284
276
|
---
|
|
285
277
|
|
|
286
278
|
## Troubleshooting
|
|
@@ -293,25 +285,19 @@ Output:
|
|
|
293
285
|
| Infinite recursion | Use `$clear` or reduce `maxRecursion` |
|
|
294
286
|
| $eval not working | Errors return empty string, use try-catch |
|
|
295
287
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
## Performance
|
|
288
|
+
## Known Bugs
|
|
299
289
|
|
|
300
|
-
-
|
|
301
|
-
- Blocks have minor overhead
|
|
302
|
-
- Avoid unlimited recursion
|
|
303
|
-
- For large inputs, use multiple contexts
|
|
290
|
+
- Multi-character block delimiters that contains double quotes doesnt match properly.
|
|
304
291
|
|
|
305
292
|
---
|
|
306
293
|
|
|
307
294
|
## Syntax Reference
|
|
308
295
|
|
|
309
296
|
```
|
|
310
|
-
pattern {$x $y} {$y, $x}
|
|
311
|
-
pattern {$x$$y} {$x-$y}
|
|
312
|
-
pattern {$block n {o}{c}} {$n}
|
|
297
|
+
pattern {$x $y} {$y, $x} # basic pattern
|
|
298
|
+
pattern {$x$$y} {$x-$y} # flexible whitespace
|
|
299
|
+
pattern {$block n {o}{c}} {$n} # block
|
|
313
300
|
context { ... } # recursive scope
|
|
314
|
-
$$literal # output literal $
|
|
315
301
|
$unique # unique ID
|
|
316
302
|
$match # full match
|
|
317
303
|
$prefix / $suffix # before/after
|
|
@@ -327,7 +313,6 @@ $eval{code} # execute JS
|
|
|
327
313
|
context {
|
|
328
314
|
# Markdown headers
|
|
329
315
|
pattern {# $title} {<h1>$title</h1>}
|
|
330
|
-
pattern {## $title} {<h2>$title</h2>}
|
|
331
316
|
|
|
332
317
|
# Lists
|
|
333
318
|
pattern {- $item} {<li>$item</li>}
|
|
@@ -338,7 +323,7 @@ context {
|
|
|
338
323
|
|
|
339
324
|
# Process content
|
|
340
325
|
# Welcome
|
|
341
|
-
|
|
326
|
+
# Getting Started
|
|
342
327
|
This is **important** and *italic*
|
|
343
328
|
- First item
|
|
344
329
|
- Second item
|
package/package.json
CHANGED
package/src/papagaio.js
CHANGED
|
@@ -8,8 +8,7 @@ export class Papagaio {
|
|
|
8
8
|
content = "";
|
|
9
9
|
|
|
10
10
|
constructor() {
|
|
11
|
-
this.#counter
|
|
12
|
-
this.#counter.unique = 0;
|
|
11
|
+
this.#counter = { value: 0, unique: 0 };
|
|
13
12
|
}
|
|
14
13
|
|
|
15
14
|
process(input) {
|
|
@@ -23,16 +22,15 @@ export class Papagaio {
|
|
|
23
22
|
while (src !== last && iter < this.maxRecursion) {
|
|
24
23
|
iter++;
|
|
25
24
|
last = src;
|
|
26
|
-
src = this.#
|
|
25
|
+
src = this.#processContext(src);
|
|
27
26
|
const [patterns, s2] = this.#collectPatterns(src);
|
|
28
|
-
src = s2;
|
|
29
|
-
src = this.#applyPatterns(src, patterns);
|
|
27
|
+
src = this.#applyPatterns(s2, patterns);
|
|
30
28
|
if (!pending()) break;
|
|
31
29
|
}
|
|
32
30
|
return this.content = src, src;
|
|
33
31
|
}
|
|
34
32
|
|
|
35
|
-
#
|
|
33
|
+
#processContext(src) {
|
|
36
34
|
const ctxRe = new RegExp(`\\b${this.keywords.context}\\s*\\${this.open}`, "g");
|
|
37
35
|
let m, matches = [];
|
|
38
36
|
while ((m = ctxRe.exec(src)) !== null)
|
|
@@ -53,103 +51,75 @@ export class Papagaio {
|
|
|
53
51
|
}
|
|
54
52
|
|
|
55
53
|
#extractBlock(src, openPos, openDelim = this.open, closeDelim = this.close) {
|
|
56
|
-
let i = openPos
|
|
57
|
-
|
|
58
|
-
// Se openDelim tem múltiplos caracteres, processa diferente
|
|
59
|
-
if (openDelim.length > 1) {
|
|
60
|
-
// Pula o delimitador de abertura
|
|
54
|
+
let i = openPos;
|
|
55
|
+
if (openDelim.length > 1 || closeDelim.length > 1) {
|
|
61
56
|
if (src.substring(i, i + openDelim.length) === openDelim) {
|
|
62
57
|
i += openDelim.length;
|
|
63
|
-
innerStart = i;
|
|
64
|
-
// Procura pelo delimitador de fechamento com balanceamento
|
|
58
|
+
const innerStart = i;
|
|
65
59
|
let d = 0;
|
|
66
60
|
while (i < src.length) {
|
|
67
61
|
if (src.substring(i, i + openDelim.length) === openDelim) {
|
|
68
62
|
d++;
|
|
69
63
|
i += openDelim.length;
|
|
70
64
|
} else if (src.substring(i, i + closeDelim.length) === closeDelim) {
|
|
71
|
-
if (d === 0)
|
|
72
|
-
return [src.substring(innerStart, i), i + closeDelim.length];
|
|
73
|
-
}
|
|
65
|
+
if (d === 0) return [src.substring(innerStart, i), i + closeDelim.length];
|
|
74
66
|
d--;
|
|
75
67
|
i += closeDelim.length;
|
|
76
|
-
} else
|
|
77
|
-
i++;
|
|
78
|
-
}
|
|
68
|
+
} else i++;
|
|
79
69
|
}
|
|
80
70
|
return [src.substring(innerStart), src.length];
|
|
81
71
|
}
|
|
82
72
|
}
|
|
83
|
-
|
|
84
|
-
// Para delimitadores single-char, balanceia
|
|
85
|
-
while (i < src.length) {
|
|
86
|
-
const ch = src[i];
|
|
87
|
-
if (inStr) {
|
|
88
|
-
if (ch === '\\') i += 2;
|
|
89
|
-
else if (ch === strChar) { inStr = false; strChar = ''; i++; }
|
|
90
|
-
else i++;
|
|
91
|
-
continue;
|
|
92
|
-
}
|
|
93
|
-
if (ch === "'" || ch === "`") {
|
|
94
|
-
inStr = true;
|
|
95
|
-
strChar = ch;
|
|
96
|
-
i++;
|
|
97
|
-
continue;
|
|
98
|
-
}
|
|
99
|
-
if (ch === openDelim) {
|
|
100
|
-
depth++;
|
|
101
|
-
if (innerStart === null) innerStart = i + 1;
|
|
102
|
-
} else if (ch === closeDelim) {
|
|
103
|
-
depth--;
|
|
104
|
-
if (depth === 0) return [innerStart !== null ? src.substring(innerStart, i) : '', i + 1];
|
|
105
|
-
}
|
|
73
|
+
if (src[i] === openDelim) {
|
|
106
74
|
i++;
|
|
75
|
+
const innerStart = i;
|
|
76
|
+
if (openDelim === closeDelim) {
|
|
77
|
+
while (i < src.length && src[i] !== closeDelim) i++;
|
|
78
|
+
return [src.substring(innerStart, i), i + 1];
|
|
79
|
+
} else {
|
|
80
|
+
let depth = 1;
|
|
81
|
+
while (i < src.length && depth > 0) {
|
|
82
|
+
if (src[i] === openDelim) depth++;
|
|
83
|
+
else if (src[i] === closeDelim) depth--;
|
|
84
|
+
if (depth > 0) i++;
|
|
85
|
+
}
|
|
86
|
+
return [src.substring(innerStart, i), i + 1];
|
|
87
|
+
}
|
|
107
88
|
}
|
|
108
|
-
return [
|
|
89
|
+
return ['', i];
|
|
109
90
|
}
|
|
110
91
|
|
|
111
|
-
#
|
|
112
|
-
|
|
92
|
+
#parsePattern(pattern) {
|
|
93
|
+
const tokens = [];
|
|
94
|
+
let i = 0;
|
|
113
95
|
const S = this.sigil, S2 = S + S;
|
|
114
96
|
while (i < pattern.length) {
|
|
115
97
|
if (pattern.startsWith(S2, i)) {
|
|
116
|
-
|
|
98
|
+
tokens.push({ type: 'whitespace-optional' });
|
|
117
99
|
i += S2.length;
|
|
118
100
|
continue;
|
|
119
101
|
}
|
|
120
102
|
if (pattern.startsWith(S + 'block', i)) {
|
|
121
103
|
let j = i + S.length + 'block'.length;
|
|
122
104
|
while (j < pattern.length && /\s/.test(pattern[j])) j++;
|
|
123
|
-
|
|
124
105
|
let varName = '';
|
|
125
|
-
while (j < pattern.length && /[A-Za-z0-9_]/.test(pattern[j]))
|
|
126
|
-
varName += pattern[j++];
|
|
127
|
-
}
|
|
128
|
-
|
|
106
|
+
while (j < pattern.length && /[A-Za-z0-9_]/.test(pattern[j])) varName += pattern[j++];
|
|
129
107
|
if (varName) {
|
|
130
108
|
while (j < pattern.length && /\s/.test(pattern[j])) j++;
|
|
131
|
-
|
|
132
|
-
// Extrair delimitador de abertura - com balanceamento
|
|
133
109
|
let openDelim = this.open;
|
|
134
110
|
if (j < pattern.length && pattern[j] === this.open) {
|
|
135
111
|
const [c, e] = this.#extractBlock(pattern, j);
|
|
136
|
-
openDelim = c.trim() || this.open;
|
|
112
|
+
openDelim = this.#unescapeDelimiter(c.trim()) || this.open;
|
|
137
113
|
j = e;
|
|
138
114
|
while (j < pattern.length && /\s/.test(pattern[j])) j++;
|
|
139
115
|
}
|
|
140
|
-
|
|
141
|
-
// Extrair delimitador de fechamento - com balanceamento
|
|
142
116
|
let closeDelim = this.close;
|
|
143
117
|
if (j < pattern.length && pattern[j] === this.open) {
|
|
144
|
-
const [c, e] = this.#extractBlock(pattern, j
|
|
145
|
-
closeDelim = c.trim() || this.close;
|
|
118
|
+
const [c, e] = this.#extractBlock(pattern, j);
|
|
119
|
+
closeDelim = this.#unescapeDelimiter(c.trim()) || this.close;
|
|
146
120
|
j = e;
|
|
147
121
|
}
|
|
148
|
-
|
|
149
|
-
const eoMask = this.#escapeRegex(openDelim);
|
|
150
|
-
const ecMask = this.#escapeRegex(closeDelim);
|
|
151
|
-
// Use greedy match to allow nested delimiters to be captured (will match up to the last closing delimiter)
|
|
152
|
-
regex += `${eoMask}([\\s\\S]*)${ecMask}`;
|
|
122
|
+
tokens.push({ type: 'block', varName, openDelim, closeDelim });
|
|
153
123
|
i = j;
|
|
154
124
|
continue;
|
|
155
125
|
}
|
|
@@ -158,92 +128,89 @@ export class Papagaio {
|
|
|
158
128
|
let j = i + S.length, varName = '';
|
|
159
129
|
while (j < pattern.length && /[A-Za-z0-9_]/.test(pattern[j])) varName += pattern[j++];
|
|
160
130
|
if (varName) {
|
|
161
|
-
|
|
131
|
+
tokens.push({ type: 'var', varName });
|
|
162
132
|
i = j;
|
|
163
|
-
|
|
164
|
-
regex += this.#escapeRegex(S);
|
|
165
|
-
i += S.length;
|
|
133
|
+
continue;
|
|
166
134
|
}
|
|
135
|
+
tokens.push({ type: 'literal', value: S });
|
|
136
|
+
i += S.length;
|
|
167
137
|
continue;
|
|
168
138
|
}
|
|
169
139
|
if (/\s/.test(pattern[i])) {
|
|
170
|
-
|
|
171
|
-
while (i < pattern.length && /\s/.test(pattern[i])) i
|
|
140
|
+
let ws = '';
|
|
141
|
+
while (i < pattern.length && /\s/.test(pattern[i])) ws += pattern[i++];
|
|
142
|
+
tokens.push({ type: 'whitespace', value: ws });
|
|
172
143
|
continue;
|
|
173
144
|
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
145
|
+
let literal = '';
|
|
146
|
+
while (i < pattern.length && !pattern.startsWith(S, i) && !/\s/.test(pattern[i])) literal += pattern[i++];
|
|
147
|
+
if (literal) tokens.push({ type: 'literal', value: literal });
|
|
177
148
|
}
|
|
178
|
-
return
|
|
149
|
+
return tokens;
|
|
179
150
|
}
|
|
180
151
|
|
|
181
|
-
#
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
while (
|
|
188
|
-
|
|
189
|
-
// Extrair nome como identificador simples
|
|
190
|
-
let varName = '';
|
|
191
|
-
while (j < pattern.length && /[A-Za-z0-9_]/.test(pattern[j])) {
|
|
192
|
-
varName += pattern[j++];
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
if (varName && !seen.has(varName)) {
|
|
196
|
-
vars.push(S + varName);
|
|
197
|
-
seen.add(varName);
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
if (varName) {
|
|
201
|
-
while (j < pattern.length && /\s/.test(pattern[j])) j++;
|
|
202
|
-
|
|
203
|
-
// Pular delimitador de abertura
|
|
204
|
-
if (j < pattern.length && pattern[j] === this.open) {
|
|
205
|
-
const [, ne] = this.#extractBlock(pattern, j);
|
|
206
|
-
j = ne;
|
|
207
|
-
while (j < pattern.length && /\s/.test(pattern[j])) j++;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
// Pular delimitador de fechamento
|
|
211
|
-
if (j < pattern.length && pattern[j] === this.open) {
|
|
212
|
-
const [, ne] = this.#extractBlock(pattern, j);
|
|
213
|
-
j = ne;
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
i = j;
|
|
152
|
+
#matchPattern(src, tokens, startPos = 0) {
|
|
153
|
+
let pos = startPos;
|
|
154
|
+
const captures = {};
|
|
155
|
+
for (let ti = 0; ti < tokens.length; ti++) {
|
|
156
|
+
const token = tokens[ti];
|
|
157
|
+
if (token.type === 'whitespace-optional') {
|
|
158
|
+
while (pos < src.length && /\s/.test(src[pos])) pos++;
|
|
218
159
|
continue;
|
|
219
160
|
}
|
|
220
|
-
if (
|
|
221
|
-
|
|
161
|
+
if (token.type === 'whitespace') {
|
|
162
|
+
if (pos >= src.length || !/\s/.test(src[pos])) return null;
|
|
163
|
+
while (pos < src.length && /\s/.test(src[pos])) pos++;
|
|
222
164
|
continue;
|
|
223
165
|
}
|
|
224
|
-
if (
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
166
|
+
if (token.type === 'literal') {
|
|
167
|
+
if (!src.startsWith(token.value, pos)) return null;
|
|
168
|
+
pos += token.value.length;
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
if (token.type === 'var') {
|
|
172
|
+
const nextToken = ti + 1 < tokens.length ? tokens[ti + 1] : null;
|
|
173
|
+
let varValue = '';
|
|
174
|
+
if (nextToken) {
|
|
175
|
+
if (nextToken.type === 'whitespace' || nextToken.type === 'whitespace-optional') {
|
|
176
|
+
while (pos < src.length && !/\s/.test(src[pos])) varValue += src[pos++];
|
|
177
|
+
} else if (nextToken.type === 'literal') {
|
|
178
|
+
const stopChar = nextToken.value[0];
|
|
179
|
+
while (pos < src.length && src[pos] !== stopChar && !/\s/.test(src[pos])) varValue += src[pos++];
|
|
180
|
+
} else if (nextToken.type === 'block') {
|
|
181
|
+
while (pos < src.length && !src.startsWith(nextToken.openDelim, pos) && !/\s/.test(src[pos])) varValue += src[pos++];
|
|
182
|
+
} else {
|
|
183
|
+
while (pos < src.length && !/\s/.test(src[pos])) varValue += src[pos++];
|
|
184
|
+
}
|
|
185
|
+
} else {
|
|
186
|
+
while (pos < src.length && !/\s/.test(src[pos])) varValue += src[pos++];
|
|
230
187
|
}
|
|
231
|
-
|
|
188
|
+
if (!varValue) return null;
|
|
189
|
+
captures[this.sigil + token.varName] = varValue;
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
if (token.type === 'block') {
|
|
193
|
+
const { varName, openDelim, closeDelim } = token;
|
|
194
|
+
if (!src.startsWith(openDelim, pos)) return null;
|
|
195
|
+
const [blockContent, endPos] = this.#extractBlock(src, pos, openDelim, closeDelim);
|
|
196
|
+
captures[this.sigil + varName] = blockContent;
|
|
197
|
+
pos = endPos;
|
|
232
198
|
continue;
|
|
233
199
|
}
|
|
234
|
-
i++;
|
|
235
200
|
}
|
|
236
|
-
return
|
|
201
|
+
return { captures, endPos: pos };
|
|
237
202
|
}
|
|
238
203
|
|
|
239
204
|
#collectPatterns(src) {
|
|
240
|
-
const patterns = []
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
205
|
+
const patterns = [];
|
|
206
|
+
const patRe = new RegExp(`\\b${this.keywords.pattern}\\s*\\${this.open}`, "g");
|
|
207
|
+
let result = src;
|
|
208
|
+
while (true) {
|
|
209
|
+
patRe.lastIndex = 0;
|
|
244
210
|
const m = patRe.exec(result);
|
|
245
211
|
if (!m) break;
|
|
246
|
-
const start = m.index
|
|
212
|
+
const start = m.index;
|
|
213
|
+
const openPos = m.index + m[0].length - 1;
|
|
247
214
|
const [matchPat, posAfterMatch] = this.#extractBlock(result, openPos);
|
|
248
215
|
let k = posAfterMatch;
|
|
249
216
|
while (k < result.length && /\s/.test(result[k])) k++;
|
|
@@ -251,10 +218,9 @@ export class Papagaio {
|
|
|
251
218
|
const [replacePat, posAfterReplace] = this.#extractBlock(result, k);
|
|
252
219
|
patterns.push({ match: matchPat.trim(), replace: replacePat.trim() });
|
|
253
220
|
result = result.slice(0, start) + result.slice(posAfterReplace);
|
|
254
|
-
i = start;
|
|
255
221
|
continue;
|
|
256
222
|
}
|
|
257
|
-
|
|
223
|
+
result = result.slice(0, start) + result.slice(posAfterMatch);
|
|
258
224
|
}
|
|
259
225
|
return [patterns, result];
|
|
260
226
|
}
|
|
@@ -262,47 +228,49 @@ export class Papagaio {
|
|
|
262
228
|
#applyPatterns(src, patterns) {
|
|
263
229
|
let clearFlag = false, lastResult = "", S = this.sigil;
|
|
264
230
|
for (const pat of patterns) {
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
const
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
for (const [k, v] of Object.entries(varMap)) {
|
|
278
|
-
const keyEsc = this.#escapeRegex(k);
|
|
279
|
-
result = result.replace(new RegExp(keyEsc + '(?![A-Za-z0-9_])', 'g'), v);
|
|
280
|
-
}
|
|
281
|
-
result = result.replace(new RegExp(`${this.#escapeRegex(S)}unique\\b`, 'g'),
|
|
282
|
-
() => this.#genUnique());
|
|
283
|
-
result = result.replace(/\$eval\{([^}]*)\}/g, (_, code) => {
|
|
284
|
-
try {
|
|
285
|
-
const wrapped = `"use strict"; return (function() { ${code} })();`;
|
|
286
|
-
return String(Function("papagaio", "ctx", wrapped)(this, {}));
|
|
287
|
-
} catch {
|
|
288
|
-
return "";
|
|
231
|
+
const tokens = this.#parsePattern(pat.match);
|
|
232
|
+
let newSrc = '';
|
|
233
|
+
let pos = 0, matched = false;
|
|
234
|
+
while (pos < src.length) {
|
|
235
|
+
const matchResult = this.#matchPattern(src, tokens, pos);
|
|
236
|
+
if (matchResult) {
|
|
237
|
+
matched = true;
|
|
238
|
+
const { captures, endPos } = matchResult;
|
|
239
|
+
let result = pat.replace;
|
|
240
|
+
for (const [k, v] of Object.entries(captures)) {
|
|
241
|
+
const keyEsc = this.#escapeRegex(k);
|
|
242
|
+
result = result.replace(new RegExp(keyEsc + '(?![A-Za-z0-9_])', 'g'), v);
|
|
289
243
|
}
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
244
|
+
result = result.replace(new RegExp(`${this.#escapeRegex(S)}unique\\b`, 'g'), () => this.#genUnique());
|
|
245
|
+
result = result.replace(/\$eval\{([^}]*)\}/g, (_, code) => {
|
|
246
|
+
try {
|
|
247
|
+
const wrapped = `"use strict"; return (function() { ${code} })();`;
|
|
248
|
+
return String(Function("papagaio", "ctx", wrapped)(this, {}));
|
|
249
|
+
} catch {
|
|
250
|
+
return "";
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
const S2 = S + S;
|
|
254
|
+
result = result.replace(new RegExp(this.#escapeRegex(S2), 'g'), '');
|
|
255
|
+
if (new RegExp(`${this.#escapeRegex(S)}clear\\b`, 'g').test(result)) {
|
|
256
|
+
result = result.replace(new RegExp(`${this.#escapeRegex(S)}clear\\b`, 'g'), '');
|
|
257
|
+
clearFlag = true;
|
|
258
|
+
}
|
|
259
|
+
const matchStart = pos, matchEnd = endPos;
|
|
260
|
+
result = result
|
|
261
|
+
.replace(new RegExp(`${this.#escapeRegex(S)}prefix\\b`, 'g'), src.slice(0, matchStart))
|
|
262
|
+
.replace(new RegExp(`${this.#escapeRegex(S)}suffix\\b`, 'g'), src.slice(matchEnd))
|
|
263
|
+
.replace(new RegExp(`${this.#escapeRegex(S)}match\\b`, 'g'), src.slice(matchStart, matchEnd));
|
|
264
|
+
newSrc += result;
|
|
265
|
+
lastResult = result;
|
|
266
|
+
pos = endPos;
|
|
267
|
+
} else {
|
|
268
|
+
newSrc += src[pos];
|
|
269
|
+
pos++;
|
|
296
270
|
}
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
.replace(new RegExp(`${this.#escapeRegex(S)}match\\b`, 'g'), fullMatch);
|
|
301
|
-
lastResult = result;
|
|
302
|
-
return result;
|
|
303
|
-
});
|
|
304
|
-
if (clearFlag) {
|
|
305
|
-
src = lastResult;
|
|
271
|
+
}
|
|
272
|
+
if (matched) {
|
|
273
|
+
src = clearFlag ? lastResult : newSrc;
|
|
306
274
|
clearFlag = false;
|
|
307
275
|
}
|
|
308
276
|
}
|
|
@@ -314,6 +282,24 @@ export class Papagaio {
|
|
|
314
282
|
}
|
|
315
283
|
|
|
316
284
|
#escapeRegex(str) {
|
|
317
|
-
return str.replace(/[.*+?^${}()|[\]
|
|
285
|
+
return str.replace(/[.*+?^${}()|[\]\\""']/g, '\\$&');
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
#unescapeDelimiter(str) {
|
|
289
|
+
let result = '';
|
|
290
|
+
for (let i = 0; i < str.length; i++) {
|
|
291
|
+
if (str[i] === '\\' && i + 1 < str.length) {
|
|
292
|
+
const next = str[i + 1];
|
|
293
|
+
if (next === '"' || next === "'" || next === '\\') {
|
|
294
|
+
result += next;
|
|
295
|
+
i++;
|
|
296
|
+
} else {
|
|
297
|
+
result += str[i];
|
|
298
|
+
}
|
|
299
|
+
} else {
|
|
300
|
+
result += str[i];
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
return result;
|
|
318
304
|
}
|
|
319
305
|
}
|
package/tests/tests.json
CHANGED
|
@@ -261,7 +261,7 @@
|
|
|
261
261
|
{
|
|
262
262
|
"id": 44,
|
|
263
263
|
"name": "Block with double quotes",
|
|
264
|
-
"code": "pattern {$block str {
|
|
264
|
+
"code": "pattern {$block str {\"}{\"}} {STR[$str]}\n\"hello\"",
|
|
265
265
|
"shouldContain": "STR[hello]"
|
|
266
266
|
},
|
|
267
267
|
{
|
|
@@ -326,7 +326,7 @@
|
|
|
326
326
|
},
|
|
327
327
|
{
|
|
328
328
|
"id": 55,
|
|
329
|
-
"name": "Block captures first simple angle block
|
|
329
|
+
"name": "Block captures first simple angle block",
|
|
330
330
|
"code": "pattern {$block txt {<}{>}} {CAP[$txt]}\nstart <one> middle <two> end",
|
|
331
331
|
"shouldContain": "CAP[one]"
|
|
332
332
|
},
|
|
@@ -344,8 +344,8 @@
|
|
|
344
344
|
},
|
|
345
345
|
{
|
|
346
346
|
"id": 58,
|
|
347
|
-
"name": "
|
|
348
|
-
"code": "pattern {$file
|
|
347
|
+
"name": "Simple Test",
|
|
348
|
+
"code": "pattern {$file.$ext} {file: $file}\nphoto.png",
|
|
349
349
|
"shouldContain": "file: photo"
|
|
350
350
|
},
|
|
351
351
|
{
|
|
@@ -362,7 +362,7 @@
|
|
|
362
362
|
},
|
|
363
363
|
{
|
|
364
364
|
"id": 61,
|
|
365
|
-
"name": "Block with pipe delimiters
|
|
365
|
+
"name": "Block with pipe delimiters",
|
|
366
366
|
"code": "pattern {$block mid {|}{|}} {PIPE[$mid]}\n|a| and |b|",
|
|
367
367
|
"shouldContain": "PIPE[a]"
|
|
368
368
|
},
|
|
@@ -380,7 +380,7 @@
|
|
|
380
380
|
},
|
|
381
381
|
{
|
|
382
382
|
"id": 64,
|
|
383
|
-
"name": "Apostrophe in pattern literal
|
|
383
|
+
"name": "Apostrophe in pattern literal",
|
|
384
384
|
"code": "pattern {It's $name} {Hello $name}\nIt's John",
|
|
385
385
|
"shouldContain": "Hello John"
|
|
386
386
|
},
|
|
@@ -401,6 +401,18 @@
|
|
|
401
401
|
"name": "Literal special regex characters in pattern",
|
|
402
402
|
"code": "pattern {a.*b} {PAT}\na.*b",
|
|
403
403
|
"shouldContain": "PAT"
|
|
404
|
+
},
|
|
405
|
+
{
|
|
406
|
+
"id": 68,
|
|
407
|
+
"name": "Block with multi-character delimiters containing double quotes(known issue)",
|
|
408
|
+
"code": "pattern {$block str {\"k}{\"k}} {STR[$str]}\n\"khello\"k",
|
|
409
|
+
"shouldContain": "STR[hello]"
|
|
410
|
+
},
|
|
411
|
+
{
|
|
412
|
+
"id": 69,
|
|
413
|
+
"name": "Block with multi-character delimiters",
|
|
414
|
+
"code": "pattern {$block str {open}{close}} {$str World!}\nopenHelloclose",
|
|
415
|
+
"shouldContain": "Hello World!"
|
|
404
416
|
}
|
|
405
417
|
]
|
|
406
418
|
}
|