papagaio 0.6.1 → 0.7.0
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 +131 -31
- package/bin/cli.mjs +153 -0
- package/examples/wasm.papagaio +2 -2
- package/index.html +588 -89
- package/package.json +2 -2
- package/src/papagaio.js +48 -49
- package/tests/tests.json +17 -17
- package/bin/cli.js +0 -34
- package/bin/cli.qjs +0 -57
package/README.md
CHANGED
|
@@ -22,8 +22,9 @@ papagaio.symbols = {
|
|
|
22
22
|
close: "}", // closing delimiter (multi-char supported)
|
|
23
23
|
sigil: "$", // variable marker
|
|
24
24
|
eval: "eval", // eval keyword
|
|
25
|
-
block: "
|
|
26
|
-
regex: "regex"
|
|
25
|
+
block: "recursive", // block keyword (recursive nesting)
|
|
26
|
+
regex: "regex", // regex keyword
|
|
27
|
+
blockseq: "sequential" // blockseq keyword (sequential blocks)
|
|
27
28
|
};
|
|
28
29
|
```
|
|
29
30
|
|
|
@@ -64,7 +65,7 @@ hello world
|
|
|
64
65
|
Output: `[hello] [world]`
|
|
65
66
|
|
|
66
67
|
```
|
|
67
|
-
$pattern {$name $
|
|
68
|
+
$pattern {$name ${(}{)}content} {$name: $content}
|
|
68
69
|
greeting (hello world)
|
|
69
70
|
```
|
|
70
71
|
Output: `greeting: hello world`
|
|
@@ -126,48 +127,104 @@ Output: `Month 03 in 2024`
|
|
|
126
127
|
|
|
127
128
|
## Blocks
|
|
128
129
|
|
|
129
|
-
|
|
130
|
+
Papagaio supports two types of block capture: **nested** and **adjacent**.
|
|
130
131
|
|
|
131
|
-
###
|
|
132
|
+
### Nested Blocks - `${open}{close}varName`
|
|
133
|
+
|
|
134
|
+
Captures content between delimiters with full nesting support. Nested delimiters are handled recursively.
|
|
135
|
+
|
|
136
|
+
#### Basic Syntax
|
|
132
137
|
```
|
|
133
|
-
$
|
|
138
|
+
${opening_delimiter}{closing_delimiter}varName
|
|
134
139
|
```
|
|
135
140
|
|
|
136
|
-
|
|
141
|
+
#### Examples
|
|
142
|
+
|
|
143
|
+
**Basic Recursive Block:**
|
|
137
144
|
```
|
|
138
|
-
$pattern {$name $
|
|
145
|
+
$pattern {$name ${(}{)}content} {[$content]}
|
|
139
146
|
data (hello world)
|
|
140
147
|
```
|
|
141
148
|
Output: `[hello world]`
|
|
142
149
|
|
|
143
|
-
|
|
150
|
+
**Custom Delimiters:**
|
|
144
151
|
```
|
|
145
|
-
$pattern {$
|
|
152
|
+
$pattern {${<<}{>>}data} {DATA: $data}
|
|
146
153
|
<<json stuff>>
|
|
147
154
|
```
|
|
148
155
|
Output: `DATA: json stuff`
|
|
149
156
|
|
|
150
|
-
|
|
157
|
+
**Multi-Character Delimiters:**
|
|
151
158
|
```
|
|
152
|
-
$pattern {$
|
|
159
|
+
$pattern {${```}{```}code} {<pre>$code</pre>}
|
|
153
160
|
```markdown
|
|
154
161
|
# Title
|
|
155
162
|
```
|
|
156
163
|
Output: `<pre># Title</pre>`
|
|
157
164
|
|
|
158
|
-
|
|
165
|
+
**Default Delimiters (empty blocks):**
|
|
166
|
+
```
|
|
167
|
+
$pattern {${}{}data} {[$data]}
|
|
168
|
+
{hello world}
|
|
169
|
+
```
|
|
170
|
+
Output: `[hello world]`
|
|
171
|
+
*(Uses default `{` and `}` when delimiters are empty)*
|
|
172
|
+
|
|
173
|
+
**Nested Blocks:**
|
|
174
|
+
```
|
|
175
|
+
$pattern {${(}{)}outer} {[$outer]}
|
|
176
|
+
(outer (inner (deep)))
|
|
177
|
+
```
|
|
178
|
+
Output: `[outer (inner (deep))]`
|
|
179
|
+
|
|
180
|
+
### Adjancent Blocks - `$${open}{close}varName`
|
|
181
|
+
|
|
182
|
+
Captures multiple adjacent blocks with the same delimiters and concatenates their content (separated by spaces).
|
|
183
|
+
|
|
184
|
+
#### Basic Syntax
|
|
159
185
|
```
|
|
160
|
-
|
|
161
|
-
(first), [second]
|
|
186
|
+
$${opening_delimiter}{closing_delimiter}varName
|
|
162
187
|
```
|
|
163
|
-
Output: `first|second`
|
|
164
188
|
|
|
165
|
-
|
|
189
|
+
#### Examples
|
|
190
|
+
|
|
191
|
+
**Basic Adjacent Block:**
|
|
192
|
+
```
|
|
193
|
+
$pattern {$${[}{]}items} {Items: $items}
|
|
194
|
+
[first][second][third]
|
|
195
|
+
```
|
|
196
|
+
Output: `Items: first second third`
|
|
197
|
+
|
|
198
|
+
**Adjacent with Custom Delimiters:**
|
|
199
|
+
```
|
|
200
|
+
$pattern {$${<}{>}tags} {Tags: $tags}
|
|
201
|
+
<html><body><div>
|
|
202
|
+
```
|
|
203
|
+
Output: `Tags: html body div`
|
|
204
|
+
|
|
205
|
+
**Default Delimiters:**
|
|
166
206
|
```
|
|
167
|
-
$pattern {
|
|
168
|
-
|
|
207
|
+
$pattern {$${}{}data} {Result: $data}
|
|
208
|
+
{a}{b}{c}
|
|
169
209
|
```
|
|
170
|
-
Output: `
|
|
210
|
+
Output: `Result: a b c`
|
|
211
|
+
|
|
212
|
+
**Mixed Usage:**
|
|
213
|
+
```
|
|
214
|
+
$pattern {${(}{)}nested, $${[}{]}seq} {Nested: $nested | Seq: $seq}
|
|
215
|
+
(a (b c)), [x][y][z]
|
|
216
|
+
```
|
|
217
|
+
Output: `Nested: a (b c) | Seq: x y z`
|
|
218
|
+
|
|
219
|
+
### Block Comparison
|
|
220
|
+
|
|
221
|
+
| Feature | Nested `${}{}var` | Adjacent `$${}{}var` |
|
|
222
|
+
|---------|---------------------|------------------------|
|
|
223
|
+
| Purpose | Capture nested content | Capture adjacent blocks |
|
|
224
|
+
| Input | `[a [b [c]]]` | `[a][b][c]` |
|
|
225
|
+
| Output | `a [b [c]]` | `a b c` |
|
|
226
|
+
| Nesting | Handled recursively | Not nested, sequential |
|
|
227
|
+
| Spacing | Preserves internal structure | Joins with spaces |
|
|
171
228
|
|
|
172
229
|
---
|
|
173
230
|
|
|
@@ -274,17 +331,18 @@ Output: `10`
|
|
|
274
331
|
* Variables automatically skip leading whitespace
|
|
275
332
|
* Trailing whitespace is trimmed when variables appear before literals
|
|
276
333
|
|
|
334
|
+
### Block Matching
|
|
335
|
+
* `${open}{close}name` = nested block capture
|
|
336
|
+
* `$${open}{close}name` = adjacent block capture (captures adjacent blocks)
|
|
337
|
+
* Supports multi-character delimiters of any length
|
|
338
|
+
* Empty delimiters `${}{}name` or `$${}{}name` use defaults from `symbols.open` and `symbols.close`
|
|
339
|
+
* Sequential blocks are joined with spaces in the captured variable
|
|
340
|
+
|
|
277
341
|
### Pattern Matching
|
|
278
342
|
* `$pattern {match} {replace}` = pattern scoped to current context
|
|
279
343
|
* Patterns inherit from parent scopes hierarchically
|
|
280
344
|
* Each `process()` call starts with a clean slate (no persistence)
|
|
281
345
|
|
|
282
|
-
### Block Matching
|
|
283
|
-
* `$block name {open}{close}` captures delimited regions
|
|
284
|
-
* Supports nested delimiters of any length
|
|
285
|
-
* Multi-character delimiters fully supported (e.g., `{>>>}{<<<}`)
|
|
286
|
-
* Blocks support arbitrary nesting depth
|
|
287
|
-
|
|
288
346
|
---
|
|
289
347
|
|
|
290
348
|
## Multi-Character Delimiter Support
|
|
@@ -305,7 +363,7 @@ Output: `[hello]`
|
|
|
305
363
|
|
|
306
364
|
### In Blocks
|
|
307
365
|
```
|
|
308
|
-
$pattern<<<$
|
|
366
|
+
$pattern<<<${<<}{>>}data>>> <<<$data>>>
|
|
309
367
|
<<content>>
|
|
310
368
|
```
|
|
311
369
|
Output: `content`
|
|
@@ -341,6 +399,27 @@ p.process(template);
|
|
|
341
399
|
// <strong>bold text</strong>
|
|
342
400
|
```
|
|
343
401
|
|
|
402
|
+
### Array/List Processor
|
|
403
|
+
```javascript
|
|
404
|
+
const p = new Papagaio();
|
|
405
|
+
const template = `
|
|
406
|
+
$pattern {$${[}{]}items} {
|
|
407
|
+
$eval{
|
|
408
|
+
const arr = '$items'.split(' ');
|
|
409
|
+
return arr.map((x, i) => \`\${i + 1}. \${x}\`).join('\\n');
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
[apple][banana][cherry]
|
|
414
|
+
`;
|
|
415
|
+
|
|
416
|
+
p.process(template);
|
|
417
|
+
// Output:
|
|
418
|
+
// 1. apple
|
|
419
|
+
// 2. banana
|
|
420
|
+
// 3. cherry
|
|
421
|
+
```
|
|
422
|
+
|
|
344
423
|
### Template System with State
|
|
345
424
|
```javascript
|
|
346
425
|
const p = new Papagaio();
|
|
@@ -371,7 +450,7 @@ p.process(template);
|
|
|
371
450
|
```javascript
|
|
372
451
|
const p = new Papagaio();
|
|
373
452
|
const template = `
|
|
374
|
-
$pattern {if $
|
|
453
|
+
$pattern {if ${(}{)}cond then ${[}{]}yes else ${<}{>}no} {
|
|
375
454
|
$eval{
|
|
376
455
|
const condition = ($cond).trim();
|
|
377
456
|
return condition === 'true' ? '$yes' : '$no';
|
|
@@ -407,6 +486,24 @@ p.process(template);
|
|
|
407
486
|
// 18
|
|
408
487
|
```
|
|
409
488
|
|
|
489
|
+
### Sequential Block Processing
|
|
490
|
+
```javascript
|
|
491
|
+
const p = new Papagaio();
|
|
492
|
+
const template = `
|
|
493
|
+
$pattern {sum $${[}{]}nums} {
|
|
494
|
+
$eval{
|
|
495
|
+
const numbers = '$nums'.split(' ').map(x => parseInt(x));
|
|
496
|
+
return numbers.reduce((a, b) => a + b, 0);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
sum [10][20][30][40]
|
|
501
|
+
`;
|
|
502
|
+
|
|
503
|
+
p.process(template);
|
|
504
|
+
// Output: 100
|
|
505
|
+
```
|
|
506
|
+
|
|
410
507
|
---
|
|
411
508
|
|
|
412
509
|
## Troubleshooting
|
|
@@ -421,6 +518,7 @@ p.process(template);
|
|
|
421
518
|
| Nested blocks fail | Ensure delimiters are properly balanced |
|
|
422
519
|
| Multi-char delimiters broken | Check delimiters don't conflict; use escaping if needed |
|
|
423
520
|
| Regex not matching | Test regex pattern separately; ensure it matches at the exact position |
|
|
521
|
+
| Empty delimiter behavior | `${}{}x` uses defaults; explicitly set if you need different behavior |
|
|
424
522
|
|
|
425
523
|
---
|
|
426
524
|
|
|
@@ -430,7 +528,9 @@ p.process(template);
|
|
|
430
528
|
$pattern {$x $y} {$y, $x} # pattern with variables
|
|
431
529
|
$pattern {$x? $y} {$y, $x} # optional variable
|
|
432
530
|
$pattern {$regex n {[0-9]+}} {$n} # regex capture
|
|
433
|
-
$pattern {$
|
|
531
|
+
$pattern {${o}{c}n} {$n} # recursive block (nested)
|
|
532
|
+
$pattern {$${o}{c}n} {$n} # sequential block (adjacent)
|
|
533
|
+
$pattern {${}{}n} {$n} # block with default delimiters
|
|
434
534
|
$eval{code} # JavaScript evaluation
|
|
435
535
|
```
|
|
436
536
|
|
|
@@ -440,7 +540,7 @@ $eval{code} # JavaScript evaluation
|
|
|
440
540
|
|
|
441
541
|
### Constructor
|
|
442
542
|
```javascript
|
|
443
|
-
new Papagaio(sigil, open, close, pattern, evalKw, blockKw, regexKw)
|
|
543
|
+
new Papagaio(sigil, open, close, pattern, evalKw, blockKw, regexKw, blockseqKw)
|
|
444
544
|
```
|
|
445
545
|
|
|
446
546
|
**Parameters:**
|
|
@@ -449,7 +549,6 @@ new Papagaio(sigil, open, close, pattern, evalKw, blockKw, regexKw)
|
|
|
449
549
|
- `close` (default: `'}'`) - Closing delimiter
|
|
450
550
|
- `pattern` (default: `'pattern'`) - Pattern keyword
|
|
451
551
|
- `evalKw` (default: `'eval'`) - Eval keyword
|
|
452
|
-
- `blockKw` (default: `'block'`) - Block keyword
|
|
453
552
|
- `regexKw` (default: `'regex'`) - Regex keyword
|
|
454
553
|
|
|
455
554
|
### Properties
|
|
@@ -475,6 +574,7 @@ p.process('$pattern {x} {y}\nx');
|
|
|
475
574
|
## Performance Notes
|
|
476
575
|
|
|
477
576
|
* Multi-character delimiter matching is optimized with substring operations
|
|
577
|
+
* Sequential blocks scan for adjacent matches without recursion overhead
|
|
478
578
|
* Nested patterns inherit parent patterns through recursive application
|
|
479
579
|
* Nested blocks and patterns have no theoretical depth limit
|
|
480
580
|
* Large recursion limits can impact performance on complex inputs
|
package/bin/cli.mjs
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Detecta o runtime
|
|
3
|
+
const isQuickJS = typeof scriptArgs !== 'undefined';
|
|
4
|
+
const isNode = typeof process !== 'undefined' && process.versions && process.versions.node;
|
|
5
|
+
|
|
6
|
+
// ============================================================================
|
|
7
|
+
// MAIN FUNCTION
|
|
8
|
+
// ============================================================================
|
|
9
|
+
async function main() {
|
|
10
|
+
// ============================================================================
|
|
11
|
+
// IMPORTS - Branch por runtime
|
|
12
|
+
// ============================================================================
|
|
13
|
+
let Papagaio, std, os, fs, pkg;
|
|
14
|
+
|
|
15
|
+
if (isQuickJS) {
|
|
16
|
+
// QuickJS imports
|
|
17
|
+
const stdModule = await import("std");
|
|
18
|
+
const osModule = await import("os");
|
|
19
|
+
std = stdModule;
|
|
20
|
+
os = osModule;
|
|
21
|
+
const { Papagaio: P } = await import("../src/papagaio.js");
|
|
22
|
+
Papagaio = P;
|
|
23
|
+
} else {
|
|
24
|
+
// Node.js imports
|
|
25
|
+
const fsModule = await import("fs");
|
|
26
|
+
fs = fsModule.default;
|
|
27
|
+
|
|
28
|
+
// Load package.json usando fs ao invés de require
|
|
29
|
+
const pkgPath = new URL("../package.json", import.meta.url);
|
|
30
|
+
const pkgContent = fs.readFileSync(pkgPath, "utf8");
|
|
31
|
+
pkg = JSON.parse(pkgContent);
|
|
32
|
+
|
|
33
|
+
const { Papagaio: P } = await import("../src/papagaio.js");
|
|
34
|
+
Papagaio = P;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// ============================================================================
|
|
38
|
+
// ABSTRAÇÃO DE CONSOLE/STD
|
|
39
|
+
// ============================================================================
|
|
40
|
+
const output = {
|
|
41
|
+
log: isQuickJS ? (msg) => std.out.puts(msg + "\n") : console.log,
|
|
42
|
+
error: isQuickJS ? (msg) => std.err.puts(msg + "\n") : console.error,
|
|
43
|
+
exit: isQuickJS ? std.exit : process.exit
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
// ============================================================================
|
|
47
|
+
// PARSE ARGUMENTS
|
|
48
|
+
// ============================================================================
|
|
49
|
+
const args = isQuickJS ? scriptArgs.slice(1) : process.argv.slice(2);
|
|
50
|
+
const VERSION = isQuickJS ? "0.6.0" : pkg.version;
|
|
51
|
+
|
|
52
|
+
// Help & Version
|
|
53
|
+
if (args.includes("-v") || args.includes("--version")) {
|
|
54
|
+
output.log(VERSION);
|
|
55
|
+
output.exit(0);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (args.includes("-h") || args.includes("--help")) {
|
|
59
|
+
output.log(`Usage: papagaio [options] <file1> [file2] [...]
|
|
60
|
+
|
|
61
|
+
Options:
|
|
62
|
+
-h, --help Show this help message
|
|
63
|
+
-v, --version Show version number
|
|
64
|
+
--sigil <symbol> Set sigil symbol
|
|
65
|
+
--open <symbol> Set open symbol
|
|
66
|
+
--close <symbol> Set close symbol
|
|
67
|
+
|
|
68
|
+
Examples:
|
|
69
|
+
papagaio input.txt
|
|
70
|
+
papagaio file1.txt file2.txt file3.txt
|
|
71
|
+
papagaio *.txt
|
|
72
|
+
papagaio --sigil @ --open [ --close ] input.txt`);
|
|
73
|
+
output.exit(0);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Parse options
|
|
77
|
+
const sigilIndex = args.findIndex(arg => arg === "--sigil");
|
|
78
|
+
const openIndex = args.findIndex(arg => arg === "--open");
|
|
79
|
+
const closeIndex = args.findIndex(arg => arg === "--close");
|
|
80
|
+
|
|
81
|
+
const sigil = sigilIndex !== -1 ? args[sigilIndex + 1] : undefined;
|
|
82
|
+
const open = openIndex !== -1 ? args[openIndex + 1] : undefined;
|
|
83
|
+
const close = closeIndex !== -1 ? args[closeIndex + 1] : undefined;
|
|
84
|
+
|
|
85
|
+
// Get input files
|
|
86
|
+
const files = args.filter((arg, i) => {
|
|
87
|
+
if (arg.startsWith("-")) return false;
|
|
88
|
+
if (i > 0 && (args[i - 1] === "--sigil" || args[i - 1] === "--open" || args[i - 1] === "--close")) return false;
|
|
89
|
+
return true;
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
if (files.length === 0) {
|
|
93
|
+
output.error("Error: no input file specified.\nUse --help for usage.");
|
|
94
|
+
output.exit(1);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// ============================================================================
|
|
98
|
+
// FILE READING ABSTRACTION
|
|
99
|
+
// ============================================================================
|
|
100
|
+
function readFile(filepath) {
|
|
101
|
+
if (isQuickJS) {
|
|
102
|
+
const f = std.open(filepath, "r");
|
|
103
|
+
if (!f) {
|
|
104
|
+
throw new Error(`cannot open file '${filepath}'`);
|
|
105
|
+
}
|
|
106
|
+
const content = f.readAsString();
|
|
107
|
+
f.close();
|
|
108
|
+
return content;
|
|
109
|
+
} else {
|
|
110
|
+
if (!fs.existsSync(filepath)) {
|
|
111
|
+
throw new Error(`file not found: ${filepath}`);
|
|
112
|
+
}
|
|
113
|
+
return fs.readFileSync(filepath, "utf8");
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// ============================================================================
|
|
118
|
+
// READ AND CONCATENATE FILES
|
|
119
|
+
// ============================================================================
|
|
120
|
+
let concatenatedSrc = "";
|
|
121
|
+
let hasErrors = false;
|
|
122
|
+
|
|
123
|
+
for (const file of files) {
|
|
124
|
+
try {
|
|
125
|
+
const src = readFile(file);
|
|
126
|
+
concatenatedSrc += src;
|
|
127
|
+
} catch (error) {
|
|
128
|
+
output.error(`Error reading ${file}: ${error.message || error}`);
|
|
129
|
+
hasErrors = true;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (hasErrors) {
|
|
134
|
+
output.exit(1);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// ============================================================================
|
|
138
|
+
// PROCESS CONCATENATED INPUT
|
|
139
|
+
// ============================================================================
|
|
140
|
+
const p = new Papagaio(sigil, open, close);
|
|
141
|
+
const out = p.process(concatenatedSrc);
|
|
142
|
+
output.log(out);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Executa main
|
|
146
|
+
main().catch(err => {
|
|
147
|
+
const output = isQuickJS
|
|
148
|
+
? (msg) => std.err.puts(msg + "\n")
|
|
149
|
+
: console.error;
|
|
150
|
+
output("Fatal error: " + (err.message || err));
|
|
151
|
+
const exit = isQuickJS ? std.exit : process.exit;
|
|
152
|
+
exit(1);
|
|
153
|
+
});
|
package/examples/wasm.papagaio
CHANGED
|
@@ -13,7 +13,7 @@ $eval{
|
|
|
13
13
|
return ""
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
$pattern {export function $name $
|
|
16
|
+
$pattern {export function $name ${(}{)}params:$rets ${}{}content} {
|
|
17
17
|
$pattern {parametrize}
|
|
18
18
|
{
|
|
19
19
|
$eval {
|
|
@@ -36,7 +36,7 @@ $pattern {export function $name $block params{(}{)}:$rets $block content{}{}} {
|
|
|
36
36
|
)
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
$pattern {function $name $
|
|
39
|
+
$pattern {function $name ${}{}params{(}{)}:$rets ${}{}content} {
|
|
40
40
|
$pattern {parametrize}
|
|
41
41
|
{
|
|
42
42
|
$eval {
|