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.
@@ -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
+ }
@@ -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.
@@ -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
+ }