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 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: "block", // block keyword
26
- regex: "regex" // regex keyword
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 $block content {(}{)}} {$name: $content}
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
- Capture content between delimiters with full nesting support.
130
+ Papagaio supports two types of block capture: **nested** and **adjacent**.
130
131
 
131
- ### Syntax
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
- $block varName {open}{close}
138
+ ${opening_delimiter}{closing_delimiter}varName
134
139
  ```
135
140
 
136
- ### Basic Example
141
+ #### Examples
142
+
143
+ **Basic Recursive Block:**
137
144
  ```
138
- $pattern {$name $block content {(}{)}} {[$content]}
145
+ $pattern {$name ${(}{)}content} {[$content]}
139
146
  data (hello world)
140
147
  ```
141
148
  Output: `[hello world]`
142
149
 
143
- ### Custom Delimiters
150
+ **Custom Delimiters:**
144
151
  ```
145
- $pattern {$block data {<<}{>>}} {DATA: $data}
152
+ $pattern {${<<}{>>}data} {DATA: $data}
146
153
  <<json stuff>>
147
154
  ```
148
155
  Output: `DATA: json stuff`
149
156
 
150
- ### Multi-Character Delimiters
157
+ **Multi-Character Delimiters:**
151
158
  ```
152
- $pattern {$block code {```}{```}} {<pre>$code</pre>}
159
+ $pattern {${```}{```}code} {<pre>$code</pre>}
153
160
  ```markdown
154
161
  # Title
155
162
  ```
156
163
  Output: `<pre># Title</pre>`
157
164
 
158
- ### Multiple Blocks
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
- $pattern {$block a {(}{)}, $block b {[}{]}} {$a|$b}
161
- (first), [second]
186
+ $${opening_delimiter}{closing_delimiter}varName
162
187
  ```
163
- Output: `first|second`
164
188
 
165
- ### Nested Blocks
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 {$block outer {(}{)}} {[$outer]}
168
- (outer (inner))
207
+ $pattern {$${}{}data} {Result: $data}
208
+ {a}{b}{c}
169
209
  ```
170
- Output: `[outer (inner)]`
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<<<$block data {<<}{>>}>>> <<<$data>>>
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 $block cond {(}{)} then $block yes {[}{]} else $block no {<}{>}} {
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 {$block n {o}{c}} {$n} # block capture with custom delimiters
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
+ });
@@ -13,7 +13,7 @@ $eval{
13
13
  return ""
14
14
  }
15
15
 
16
- $pattern {export function $name $block params{(}{)}:$rets $block content{}{}} {
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 $block params{(}{)}:$rets $block content{}{}} {
39
+ $pattern {function $name ${}{}params{(}{)}:$rets ${}{}content} {
40
40
  $pattern {parametrize}
41
41
  {
42
42
  $eval {