precedent 1.0.14 → 1.0.16
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/.babelrc +5 -0
- package/CONTRIBUTING.md +50 -0
- package/README.md +116 -43
- package/docs/.nojekyll +0 -0
- package/docs/README.md +142 -0
- package/docs/_sidebar.md +19 -0
- package/docs/_topbar.md +5 -0
- package/docs/api.md +209 -0
- package/docs/cover.md +15 -0
- package/docs/css/docuserve.css +73 -0
- package/docs/examples.md +429 -0
- package/docs/index.html +39 -0
- package/docs/retold-catalog.json +70 -0
- package/docs/retold-keyword-index.json +19 -0
- package/package.json +49 -62
- package/source/StringParser.js +92 -61
- package/source/WordTree.js +50 -12
- package/test/Precedent_tests.js +0 -3
- package/.browserslistrc_compatible +0 -1
- package/.browserslistrc_default +0 -1
- package/.travis.yml +0 -13
- package/Ideas.md +0 -4
- package/bower.json +0 -27
- package/dist/precedent.compatible.js +0 -310
- package/dist/precedent.compatible.min.js +0 -12
- package/dist/precedent.compatible.min.js.map +0 -1
- package/dist/precedent.js +0 -310
- package/dist/precedent.min.js +0 -12
- package/dist/precedent.min.js.map +0 -1
- package/gulpfile-config.json +0 -11
- package/gulpfile-config_compatible.json +0 -11
- package/gulpfile-config_default.json +0 -11
- package/gulpfile.js +0 -164
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/* ============================================================================
|
|
2
|
+
Pict Docuserve - Base Styles
|
|
3
|
+
============================================================================ */
|
|
4
|
+
|
|
5
|
+
/* Reset and base */
|
|
6
|
+
*, *::before, *::after {
|
|
7
|
+
box-sizing: border-box;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
html, body {
|
|
11
|
+
margin: 0;
|
|
12
|
+
padding: 0;
|
|
13
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
14
|
+
font-size: 16px;
|
|
15
|
+
line-height: 1.5;
|
|
16
|
+
color: #423D37;
|
|
17
|
+
background-color: #fff;
|
|
18
|
+
-webkit-font-smoothing: antialiased;
|
|
19
|
+
-moz-osx-font-smoothing: grayscale;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/* Typography */
|
|
23
|
+
h1, h2, h3, h4, h5, h6 {
|
|
24
|
+
margin-top: 0;
|
|
25
|
+
line-height: 1.3;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
a {
|
|
29
|
+
color: #2E7D74;
|
|
30
|
+
text-decoration: none;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
a:hover {
|
|
34
|
+
color: #256861;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/* Application container */
|
|
38
|
+
#Docuserve-Application-Container {
|
|
39
|
+
min-height: 100vh;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/* Utility: scrollbar styling */
|
|
43
|
+
::-webkit-scrollbar {
|
|
44
|
+
width: 8px;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
::-webkit-scrollbar-track {
|
|
48
|
+
background: #F5F0E8;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
::-webkit-scrollbar-thumb {
|
|
52
|
+
background: #D4CCBE;
|
|
53
|
+
border-radius: 4px;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
::-webkit-scrollbar-thumb:hover {
|
|
57
|
+
background: #B5AA9A;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/* Responsive adjustments */
|
|
61
|
+
@media (max-width: 768px) {
|
|
62
|
+
html {
|
|
63
|
+
font-size: 14px;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
#Docuserve-Sidebar-Container {
|
|
67
|
+
display: none;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.docuserve-body {
|
|
71
|
+
flex-direction: column;
|
|
72
|
+
}
|
|
73
|
+
}
|
package/docs/examples.md
ADDED
|
@@ -0,0 +1,429 @@
|
|
|
1
|
+
# Usage Patterns
|
|
2
|
+
|
|
3
|
+
Progressively complex examples showing how to use Precedent's pattern matching in real applications.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Static Replacement
|
|
8
|
+
|
|
9
|
+
The simplest use: replace every occurrence of a delimited region with a fixed string.
|
|
10
|
+
|
|
11
|
+
```javascript
|
|
12
|
+
const libPrecedent = require('precedent');
|
|
13
|
+
let processor = new libPrecedent();
|
|
14
|
+
|
|
15
|
+
processor.addPattern('<%', '%>', 'REDACTED');
|
|
16
|
+
|
|
17
|
+
processor.parseString('The API key is <%abc123xyz%> in the config.');
|
|
18
|
+
// => "The API key is REDACTED in the config."
|
|
19
|
+
|
|
20
|
+
processor.parseString('No matches here.');
|
|
21
|
+
// => "No matches here."
|
|
22
|
+
|
|
23
|
+
processor.parseString('A <% first %> and B <% second %> end.');
|
|
24
|
+
// => "A REDACTED and B REDACTED end."
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Every region between `<%` and `%>` is replaced regardless of its content. The content is discarded.
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Self-Closing Delimiters
|
|
32
|
+
|
|
33
|
+
When you pass only a start delimiter and omit the end, the same string is used for both:
|
|
34
|
+
|
|
35
|
+
```javascript
|
|
36
|
+
let processor = new libPrecedent();
|
|
37
|
+
|
|
38
|
+
processor.addPattern('$');
|
|
39
|
+
|
|
40
|
+
processor.parseString('Remove the $dollar signs$ from this.');
|
|
41
|
+
// => "Remove the dollar signs from this."
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
The `$...$` pair is matched and the content between them passes through (since no handler was provided, the default echoes the content). The dollar signs themselves are stripped.
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## Content-Processing Functions
|
|
49
|
+
|
|
50
|
+
Pass a function as the third argument to transform the matched content:
|
|
51
|
+
|
|
52
|
+
```javascript
|
|
53
|
+
let processor = new libPrecedent();
|
|
54
|
+
|
|
55
|
+
// Count characters between the delimiters
|
|
56
|
+
processor.addPattern('<%#', '%>',
|
|
57
|
+
(pContent) =>
|
|
58
|
+
{
|
|
59
|
+
return pContent.length;
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
processor.parseString('There are <%#0123456789%> digits.');
|
|
63
|
+
// => "There are 10 digits."
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
The function receives the text between the delimiters (with delimiters stripped). Whatever it returns becomes the replacement.
|
|
67
|
+
|
|
68
|
+
### More Transform Examples
|
|
69
|
+
|
|
70
|
+
```javascript
|
|
71
|
+
// Uppercase
|
|
72
|
+
processor.addPattern('{upper:', '}',
|
|
73
|
+
(pContent) =>
|
|
74
|
+
{
|
|
75
|
+
return pContent.toUpperCase();
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
processor.parseString('{upper:hello world}');
|
|
79
|
+
// => "HELLO WORLD"
|
|
80
|
+
|
|
81
|
+
// Reverse
|
|
82
|
+
processor.addPattern('{rev:', '}',
|
|
83
|
+
(pContent) =>
|
|
84
|
+
{
|
|
85
|
+
return pContent.split('').reverse().join('');
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
processor.parseString('{rev:abcde}');
|
|
89
|
+
// => "edcba"
|
|
90
|
+
|
|
91
|
+
// Base64 encode
|
|
92
|
+
processor.addPattern('{b64:', '}',
|
|
93
|
+
(pContent) =>
|
|
94
|
+
{
|
|
95
|
+
return Buffer.from(pContent).toString('base64');
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
processor.parseString('{b64:Hello}');
|
|
99
|
+
// => "SGVsbG8="
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## Data Passing
|
|
105
|
+
|
|
106
|
+
The second argument to `parseString()` is passed to every handler as its second parameter. This is how you inject external data into template processing.
|
|
107
|
+
|
|
108
|
+
### Simple Data Object
|
|
109
|
+
|
|
110
|
+
```javascript
|
|
111
|
+
let processor = new libPrecedent();
|
|
112
|
+
|
|
113
|
+
processor.addPattern('{', '}',
|
|
114
|
+
(pContent, pData) =>
|
|
115
|
+
{
|
|
116
|
+
return pData[pContent] || '';
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
let result = processor.parseString(
|
|
120
|
+
'Dear {name}, your order #{orderId} is ready.',
|
|
121
|
+
{ name: 'Bob', orderId: '12345' }
|
|
122
|
+
);
|
|
123
|
+
// => "Dear Bob, your order #12345 is ready."
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Nested Object Access
|
|
127
|
+
|
|
128
|
+
```javascript
|
|
129
|
+
let processor = new libPrecedent();
|
|
130
|
+
|
|
131
|
+
processor.addPattern('<^', '^>',
|
|
132
|
+
(pContent, pData) =>
|
|
133
|
+
{
|
|
134
|
+
// Simple dot-notation resolver
|
|
135
|
+
let tmpParts = pContent.split('.');
|
|
136
|
+
let tmpValue = pData;
|
|
137
|
+
for (let i = 0; i < tmpParts.length; i++)
|
|
138
|
+
{
|
|
139
|
+
if (tmpValue && tmpValue.hasOwnProperty(tmpParts[i]))
|
|
140
|
+
{
|
|
141
|
+
tmpValue = tmpValue[tmpParts[i]];
|
|
142
|
+
}
|
|
143
|
+
else
|
|
144
|
+
{
|
|
145
|
+
return '';
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return String(tmpValue);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
let data =
|
|
152
|
+
{
|
|
153
|
+
User: { Name: 'Alice', Role: 'Admin' },
|
|
154
|
+
App: { Version: '2.1.0' }
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
processor.parseString('Hello <^User.Name^>, you are a <^User.Role^>. App v<^App.Version^>.', data);
|
|
158
|
+
// => "Hello Alice, you are a Admin. App v2.1.0."
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### Scalar Data
|
|
162
|
+
|
|
163
|
+
The data argument does not have to be an object:
|
|
164
|
+
|
|
165
|
+
```javascript
|
|
166
|
+
let processor = new libPrecedent();
|
|
167
|
+
|
|
168
|
+
processor.addPattern('<*', '*>',
|
|
169
|
+
(pContent, pData) =>
|
|
170
|
+
{
|
|
171
|
+
return `${pContent}=${pData}`;
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
processor.parseString('Value: <*x*>', 42);
|
|
175
|
+
// => "Value: x=42"
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
## Multiple Patterns
|
|
181
|
+
|
|
182
|
+
A single Precedent instance can hold any number of patterns with different delimiters. Each is matched independently:
|
|
183
|
+
|
|
184
|
+
```javascript
|
|
185
|
+
let processor = new libPrecedent();
|
|
186
|
+
|
|
187
|
+
// Static replacement
|
|
188
|
+
processor.addPattern('<%', '%>', 'JUNKED');
|
|
189
|
+
|
|
190
|
+
// Character count
|
|
191
|
+
processor.addPattern('<%#', '%>',
|
|
192
|
+
(pContent) =>
|
|
193
|
+
{
|
|
194
|
+
return pContent.length;
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
// Self-closing comment stripper
|
|
198
|
+
processor.addPattern('$');
|
|
199
|
+
|
|
200
|
+
// Data lookup
|
|
201
|
+
processor.addPattern('<^', '^>',
|
|
202
|
+
(pContent, pData) =>
|
|
203
|
+
{
|
|
204
|
+
return pData[pContent] || '???';
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
let result = processor.parseString(
|
|
208
|
+
'Count: <%#12345%>, data: <^key^>, junk: <% removed %>, comment: $gone$',
|
|
209
|
+
{ key: 'found' }
|
|
210
|
+
);
|
|
211
|
+
// => "Count: 5, data: found, junk: JUNKED, comment: gone"
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
The word tree handles all patterns simultaneously during a single pass through the string.
|
|
215
|
+
|
|
216
|
+
---
|
|
217
|
+
|
|
218
|
+
## Pattern Precedence
|
|
219
|
+
|
|
220
|
+
When multiple patterns share a prefix, the longest match takes priority. If the longer match fails partway through, the parser falls back to the shorter one:
|
|
221
|
+
|
|
222
|
+
```javascript
|
|
223
|
+
let processor = new libPrecedent();
|
|
224
|
+
|
|
225
|
+
processor.addPattern('<', '>', 'SHORT');
|
|
226
|
+
processor.addPattern('<<', '>', 'MEDIUM');
|
|
227
|
+
processor.addPattern('<<EXTRALONG', '>', 'LONG');
|
|
228
|
+
|
|
229
|
+
processor.parseString('<x>');
|
|
230
|
+
// => "SHORT"
|
|
231
|
+
|
|
232
|
+
processor.parseString('<<x>');
|
|
233
|
+
// => "MEDIUM"
|
|
234
|
+
|
|
235
|
+
processor.parseString('<<EXTRALONG>');
|
|
236
|
+
// => "LONG"
|
|
237
|
+
|
|
238
|
+
processor.parseString('<<here>');
|
|
239
|
+
// => "MEDIUM"
|
|
240
|
+
|
|
241
|
+
processor.parseString('<<<<>');
|
|
242
|
+
// => "MEDIUM"
|
|
243
|
+
// First << matches MEDIUM, then << begins a new match, then > closes it → MEDIUM
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
This behavior is automatic — the word tree disambiguates overlapping prefixes without any explicit priority configuration.
|
|
247
|
+
|
|
248
|
+
---
|
|
249
|
+
|
|
250
|
+
## Environment Variable Substitution
|
|
251
|
+
|
|
252
|
+
This is the real-world pattern used by Fable Settings to inject environment variables into configuration files:
|
|
253
|
+
|
|
254
|
+
```javascript
|
|
255
|
+
let processor = new libPrecedent();
|
|
256
|
+
|
|
257
|
+
processor.addPattern('${', '}',
|
|
258
|
+
(pTemplateValue) =>
|
|
259
|
+
{
|
|
260
|
+
let tmpValue = pTemplateValue.trim();
|
|
261
|
+
let tmpSeparatorIndex = tmpValue.indexOf('|');
|
|
262
|
+
|
|
263
|
+
let tmpDefaultValue = (tmpSeparatorIndex >= 0) ? tmpValue.substring(tmpSeparatorIndex + 1) : '';
|
|
264
|
+
let tmpVarName = (tmpSeparatorIndex > -1) ? tmpValue.substring(0, tmpSeparatorIndex) : tmpValue;
|
|
265
|
+
|
|
266
|
+
if (tmpVarName in process.env)
|
|
267
|
+
{
|
|
268
|
+
return process.env[tmpVarName];
|
|
269
|
+
}
|
|
270
|
+
else
|
|
271
|
+
{
|
|
272
|
+
return tmpDefaultValue;
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
### Usage
|
|
278
|
+
|
|
279
|
+
```javascript
|
|
280
|
+
// Real environment variable
|
|
281
|
+
processor.parseString('Path is: ${PATH}');
|
|
282
|
+
// => "Path is: /usr/local/bin:/usr/bin:..."
|
|
283
|
+
|
|
284
|
+
// Missing variable with default
|
|
285
|
+
processor.parseString('DB host: ${DATABASE_HOST|localhost}');
|
|
286
|
+
// => "DB host: localhost"
|
|
287
|
+
|
|
288
|
+
// Missing variable without default
|
|
289
|
+
processor.parseString('Secret: ${MISSING_VAR}');
|
|
290
|
+
// => "Secret: "
|
|
291
|
+
|
|
292
|
+
// Multiple in one string
|
|
293
|
+
processor.parseString('${DATABASE_HOST|localhost}:${DATABASE_PORT|5432}');
|
|
294
|
+
// => "localhost:5432"
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
The pipe (`|`) separates the variable name from the default value. Whitespace around the variable name is trimmed. This is exactly how Fable Settings processes configuration strings.
|
|
298
|
+
|
|
299
|
+
---
|
|
300
|
+
|
|
301
|
+
## Conditional Output
|
|
302
|
+
|
|
303
|
+
Build a simple conditional by checking the content against the data:
|
|
304
|
+
|
|
305
|
+
```javascript
|
|
306
|
+
let processor = new libPrecedent();
|
|
307
|
+
|
|
308
|
+
processor.addPattern('{?', '?}',
|
|
309
|
+
(pContent, pData) =>
|
|
310
|
+
{
|
|
311
|
+
// Format: condition|trueOutput|falseOutput
|
|
312
|
+
let tmpParts = pContent.split('|');
|
|
313
|
+
if (tmpParts.length < 3) return '';
|
|
314
|
+
|
|
315
|
+
let tmpConditionKey = tmpParts[0].trim();
|
|
316
|
+
let tmpTrueOutput = tmpParts[1];
|
|
317
|
+
let tmpFalseOutput = tmpParts[2];
|
|
318
|
+
|
|
319
|
+
return pData[tmpConditionKey] ? tmpTrueOutput : tmpFalseOutput;
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
processor.parseString('{?loggedIn|Welcome back|Please log in?}', { loggedIn: true });
|
|
323
|
+
// => "Welcome back"
|
|
324
|
+
|
|
325
|
+
processor.parseString('{?loggedIn|Welcome back|Please log in?}', { loggedIn: false });
|
|
326
|
+
// => "Please log in"
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
---
|
|
330
|
+
|
|
331
|
+
## HTML Generation
|
|
332
|
+
|
|
333
|
+
Use patterns to build HTML from data:
|
|
334
|
+
|
|
335
|
+
```javascript
|
|
336
|
+
let processor = new libPrecedent();
|
|
337
|
+
|
|
338
|
+
processor.addPattern('{link:', '}',
|
|
339
|
+
(pContent, pData) =>
|
|
340
|
+
{
|
|
341
|
+
// Format: url|text
|
|
342
|
+
let tmpParts = pContent.split('|');
|
|
343
|
+
let tmpUrl = tmpParts[0];
|
|
344
|
+
let tmpText = tmpParts.length > 1 ? tmpParts[1] : tmpUrl;
|
|
345
|
+
return `<a href="${tmpUrl}">${tmpText}</a>`;
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
processor.addPattern('{img:', '}',
|
|
349
|
+
(pContent) =>
|
|
350
|
+
{
|
|
351
|
+
return `<img src="${pContent}" alt="" />`;
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
processor.parseString('Visit {link:https://example.com|our site} or see {img:/logo.png}');
|
|
355
|
+
// => 'Visit <a href="https://example.com">our site</a> or see <img src="/logo.png" alt="" />'
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
---
|
|
359
|
+
|
|
360
|
+
## Markdown-Like Syntax
|
|
361
|
+
|
|
362
|
+
Create lightweight markup that transforms to HTML:
|
|
363
|
+
|
|
364
|
+
```javascript
|
|
365
|
+
let processor = new libPrecedent();
|
|
366
|
+
|
|
367
|
+
processor.addPattern('**', '**',
|
|
368
|
+
(pContent) =>
|
|
369
|
+
{
|
|
370
|
+
return `<strong>${pContent}</strong>`;
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
processor.addPattern('__', '__',
|
|
374
|
+
(pContent) =>
|
|
375
|
+
{
|
|
376
|
+
return `<em>${pContent}</em>`;
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
processor.addPattern('`', '`',
|
|
380
|
+
(pContent) =>
|
|
381
|
+
{
|
|
382
|
+
return `<code>${pContent}</code>`;
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
processor.parseString('This is **bold** and __italic__ and `code`.');
|
|
386
|
+
// => "This is <strong>bold</strong> and <em>italic</em> and <code>code</code>."
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
---
|
|
390
|
+
|
|
391
|
+
## Composing Multiple Passes
|
|
392
|
+
|
|
393
|
+
For scenarios where one pass produces patterns that should be processed by a second pass, chain multiple Precedent instances:
|
|
394
|
+
|
|
395
|
+
```javascript
|
|
396
|
+
// First pass: resolve variables
|
|
397
|
+
let envProcessor = new libPrecedent();
|
|
398
|
+
envProcessor.addPattern('${', '}',
|
|
399
|
+
(pContent) =>
|
|
400
|
+
{
|
|
401
|
+
return process.env[pContent] || '';
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
// Second pass: format values
|
|
405
|
+
let formatProcessor = new libPrecedent();
|
|
406
|
+
formatProcessor.addPattern('{upper:', '}',
|
|
407
|
+
(pContent) =>
|
|
408
|
+
{
|
|
409
|
+
return pContent.toUpperCase();
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
let template = '{upper:${USER}}';
|
|
413
|
+
let afterEnv = envProcessor.parseString(template);
|
|
414
|
+
// => "{upper:alice}" (if USER=alice)
|
|
415
|
+
let final = formatProcessor.parseString(afterEnv);
|
|
416
|
+
// => "ALICE"
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
Each Precedent instance is independent — they do not share patterns or state.
|
|
420
|
+
|
|
421
|
+
---
|
|
422
|
+
|
|
423
|
+
## Tips
|
|
424
|
+
|
|
425
|
+
- **Delimiter choice** — Pick delimiters that do not appear naturally in your text. Multi-character delimiters (like `<%` or `{~`) are safer than single characters.
|
|
426
|
+
- **Handler return type** — Handlers must return a string (or a value that coerces to string). Returning `undefined` or `null` produces the string `"undefined"` or `"null"`.
|
|
427
|
+
- **No nesting** — Precedent does not support nested patterns of the same type. If `{` is a start delimiter, a `{` inside the content is treated as content, not a new match.
|
|
428
|
+
- **Single pass** — Parsing happens in one pass. If a handler's output contains delimiters, they are not re-processed (unless you run a second `parseString()` call).
|
|
429
|
+
- **Thread safety** — Instances are not shared state. Create one per context if needed.
|
package/docs/index.html
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
|
7
|
+
<meta name="description" content="Documentation powered by pict-docuserve">
|
|
8
|
+
|
|
9
|
+
<title>Documentation</title>
|
|
10
|
+
|
|
11
|
+
<!-- Application Stylesheet -->
|
|
12
|
+
<link href="css/docuserve.css" rel="stylesheet">
|
|
13
|
+
<!-- KaTeX stylesheet for LaTeX equation rendering -->
|
|
14
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.21/dist/katex.min.css">
|
|
15
|
+
<!-- PICT Dynamic View CSS Container -->
|
|
16
|
+
<style id="PICT-CSS"></style>
|
|
17
|
+
|
|
18
|
+
<!-- Load the PICT library from jsDelivr CDN -->
|
|
19
|
+
<script src="https://cdn.jsdelivr.net/npm/pict@1/dist/pict.min.js" type="text/javascript"></script>
|
|
20
|
+
<!-- Bootstrap the Application -->
|
|
21
|
+
<script type="text/javascript">
|
|
22
|
+
//<![CDATA[
|
|
23
|
+
Pict.safeOnDocumentReady(() => { Pict.safeLoadPictApplication(PictDocuserve, 2)});
|
|
24
|
+
//]]>
|
|
25
|
+
</script>
|
|
26
|
+
</head>
|
|
27
|
+
<body>
|
|
28
|
+
<!-- The root container for the Pict application -->
|
|
29
|
+
<div id="Docuserve-Application-Container"></div>
|
|
30
|
+
|
|
31
|
+
<!-- Mermaid diagram rendering -->
|
|
32
|
+
<script src="https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.min.js"></script>
|
|
33
|
+
<script>mermaid.initialize({ startOnLoad: false, theme: 'default' });</script>
|
|
34
|
+
<!-- KaTeX for LaTeX equation rendering -->
|
|
35
|
+
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.21/dist/katex.min.js"></script>
|
|
36
|
+
<!-- Load the Docuserve PICT Application Bundle from jsDelivr CDN -->
|
|
37
|
+
<script src="https://cdn.jsdelivr.net/npm/pict-docuserve@0/dist/pict-docuserve.min.js" type="text/javascript"></script>
|
|
38
|
+
</body>
|
|
39
|
+
</html>
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
{
|
|
2
|
+
"Generated": "2026-02-18T05:35:55.193Z",
|
|
3
|
+
"GitHubOrg": "stevenvelozo",
|
|
4
|
+
"DefaultBranch": "master",
|
|
5
|
+
"Groups": [
|
|
6
|
+
{
|
|
7
|
+
"Name": ".config",
|
|
8
|
+
"Key": ".config",
|
|
9
|
+
"Description": "",
|
|
10
|
+
"Modules": [
|
|
11
|
+
{
|
|
12
|
+
"Name": "code-server",
|
|
13
|
+
"Repo": "code-server",
|
|
14
|
+
"Group": ".config",
|
|
15
|
+
"Branch": "master",
|
|
16
|
+
"HasDocs": false,
|
|
17
|
+
"HasCover": false,
|
|
18
|
+
"Sidebar": [],
|
|
19
|
+
"DocFiles": []
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"Name": "configstore",
|
|
23
|
+
"Repo": "configstore",
|
|
24
|
+
"Group": ".config",
|
|
25
|
+
"Branch": "master",
|
|
26
|
+
"HasDocs": false,
|
|
27
|
+
"HasCover": false,
|
|
28
|
+
"Sidebar": [],
|
|
29
|
+
"DocFiles": []
|
|
30
|
+
}
|
|
31
|
+
]
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"Name": "Dist",
|
|
35
|
+
"Key": "dist",
|
|
36
|
+
"Description": "",
|
|
37
|
+
"Modules": [
|
|
38
|
+
{
|
|
39
|
+
"Name": "indoctrinate_content_staging",
|
|
40
|
+
"Repo": "indoctrinate_content_staging",
|
|
41
|
+
"Group": "dist",
|
|
42
|
+
"Branch": "master",
|
|
43
|
+
"HasDocs": false,
|
|
44
|
+
"HasCover": false,
|
|
45
|
+
"Sidebar": [],
|
|
46
|
+
"DocFiles": []
|
|
47
|
+
}
|
|
48
|
+
]
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"Name": "Docs",
|
|
52
|
+
"Key": "docs",
|
|
53
|
+
"Description": "",
|
|
54
|
+
"Modules": [
|
|
55
|
+
{
|
|
56
|
+
"Name": "css",
|
|
57
|
+
"Repo": "css",
|
|
58
|
+
"Group": "docs",
|
|
59
|
+
"Branch": "master",
|
|
60
|
+
"HasDocs": true,
|
|
61
|
+
"HasCover": false,
|
|
62
|
+
"Sidebar": [],
|
|
63
|
+
"DocFiles": [
|
|
64
|
+
"css/docuserve.css"
|
|
65
|
+
]
|
|
66
|
+
}
|
|
67
|
+
]
|
|
68
|
+
}
|
|
69
|
+
]
|
|
70
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"Generated": "2026-02-15T19:32:18.050Z",
|
|
3
|
+
"DocumentCount": 0,
|
|
4
|
+
"LunrIndex": {
|
|
5
|
+
"version": "2.3.9",
|
|
6
|
+
"fields": [
|
|
7
|
+
"title",
|
|
8
|
+
"module",
|
|
9
|
+
"group",
|
|
10
|
+
"body"
|
|
11
|
+
],
|
|
12
|
+
"fieldVectors": [],
|
|
13
|
+
"invertedIndex": [],
|
|
14
|
+
"pipeline": [
|
|
15
|
+
"stemmer"
|
|
16
|
+
]
|
|
17
|
+
},
|
|
18
|
+
"Documents": {}
|
|
19
|
+
}
|