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
package/CONTRIBUTING.md
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Contributing to Retold
|
|
2
|
+
|
|
3
|
+
We welcome contributions to Retold and its modules. This guide covers the expectations and process for contributing.
|
|
4
|
+
|
|
5
|
+
## Code of Conduct
|
|
6
|
+
|
|
7
|
+
The Retold community values **empathy**, **equity**, **kindness**, and **thoughtfulness**. We expect all participants to treat each other with respect, assume good intent, and engage constructively. These values apply to all interactions: pull requests, issues, discussions, and code review.
|
|
8
|
+
|
|
9
|
+
## How to Contribute
|
|
10
|
+
|
|
11
|
+
### Pull Requests
|
|
12
|
+
|
|
13
|
+
Pull requests are the preferred method for contributing changes. To submit one:
|
|
14
|
+
|
|
15
|
+
1. Fork the module repository you want to change
|
|
16
|
+
2. Create a branch for your work
|
|
17
|
+
3. Make your changes, following the code style of the module you are editing
|
|
18
|
+
4. Ensure your changes have test coverage (see below)
|
|
19
|
+
5. Open a pull request against the module's main branch
|
|
20
|
+
|
|
21
|
+
**Submitting a pull request does not guarantee it will be accepted.** Maintainers review contributions for fit, quality, and alignment with the project's direction. A PR may be declined, or you may be asked to revise it. This is normal and not a reflection on the quality of your effort.
|
|
22
|
+
|
|
23
|
+
### Reporting Issues
|
|
24
|
+
|
|
25
|
+
If you find a bug or have a feature suggestion, open an issue on the relevant module's repository. Include enough detail to reproduce the problem or understand the proposal.
|
|
26
|
+
|
|
27
|
+
## Test Coverage
|
|
28
|
+
|
|
29
|
+
Every commit must include test coverage for the changes it introduces. Retold modules use Mocha in TDD style. Before submitting:
|
|
30
|
+
|
|
31
|
+
- **Write tests** for any new functionality or bug fixes
|
|
32
|
+
- **Run the existing test suite** with `npm test` and confirm all tests pass
|
|
33
|
+
- **Check coverage** with `npm run coverage` if the module supports it
|
|
34
|
+
|
|
35
|
+
Pull requests that break existing tests or lack coverage for new code will not be merged.
|
|
36
|
+
|
|
37
|
+
## Code Style
|
|
38
|
+
|
|
39
|
+
Follow the conventions of the module you are working in. The general Retold style is:
|
|
40
|
+
|
|
41
|
+
- **Tabs** for indentation, never spaces
|
|
42
|
+
- **Plain JavaScript** only (no TypeScript)
|
|
43
|
+
- **Allman-style braces** (opening brace on its own line)
|
|
44
|
+
- **Variable naming:** `pVariable` for parameters, `tmpVariable` for temporaries, `libSomething` for imports
|
|
45
|
+
|
|
46
|
+
When in doubt, match what the surrounding code does.
|
|
47
|
+
|
|
48
|
+
## Repository Structure
|
|
49
|
+
|
|
50
|
+
Each module is its own git repository. The [retold](https://github.com/stevenvelozo/retold) repository tracks module organization but does not contain module source code. Direct your pull request to the specific module repository where your change belongs.
|
package/README.md
CHANGED
|
@@ -1,76 +1,149 @@
|
|
|
1
1
|
# Precedent
|
|
2
|
-
Precedent meta-templating engine, for when you want templates ... for templates.
|
|
3
2
|
|
|
4
|
-
|
|
3
|
+
A meta-templating engine for processing text streams with pattern-based template expressions. Define start/end pattern markers with string or function parsers, and Precedent handles nested pattern resolution automatically.
|
|
5
4
|
|
|
6
|
-
|
|
5
|
+
[](https://coveralls.io/github/stevenvelozo/precedent?branch=master)
|
|
6
|
+
[](https://github.com/stevenvelozo/precedent/actions)
|
|
7
|
+
[](https://badge.fury.io/js/precedent)
|
|
8
|
+
[](https://opensource.org/licenses/MIT)
|
|
7
9
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
So, for instance, you could create a pattern like so:
|
|
10
|
+
---
|
|
11
11
|
|
|
12
|
+
## Features
|
|
12
13
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
- **Pattern-Based Parsing** - Define start/end markers to identify template regions in text
|
|
15
|
+
- **String or Function Parsers** - Replace patterns with static strings or dynamic function output
|
|
16
|
+
- **Nested Pattern Support** - Overlapping pattern prefixes (e.g. `<%`, `<%=`, `<$$`) resolve correctly in a single pass
|
|
17
|
+
- **Word Tree Architecture** - Patterns are stored in a tree structure for efficient matching
|
|
18
|
+
- **Data Passing** - Pass a data object through to parser functions for context-aware rendering
|
|
19
|
+
- **Browser Compatible** - Works in both Node.js and browser environments
|
|
20
|
+
- **Zero Dependencies** - No external runtime dependencies
|
|
16
21
|
|
|
17
|
-
|
|
18
|
-
libPrecedent.addPattern('{Name', '}', 'David Bowie');
|
|
22
|
+
## Installation
|
|
19
23
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
// Anything inbetween the start and end is ignored in this case, since it is a string substitution.
|
|
23
|
-
console.log(libPrecedent.parseString('This is just a short message for {Name THIS TEXT IS IGNORED}. We hope to ignore the previous text.');
|
|
24
|
+
```bash
|
|
25
|
+
npm install precedent
|
|
24
26
|
```
|
|
25
27
|
|
|
26
|
-
|
|
28
|
+
## Quick Start
|
|
29
|
+
|
|
30
|
+
```javascript
|
|
31
|
+
const Precedent = require('precedent');
|
|
32
|
+
|
|
33
|
+
const precedent = new Precedent();
|
|
27
34
|
|
|
35
|
+
// Add a simple string substitution pattern
|
|
36
|
+
precedent.addPattern('{Name', '}', 'David Bowie');
|
|
37
|
+
|
|
38
|
+
// Parse a string containing the pattern
|
|
39
|
+
precedent.parseString('Hello, {Name}!');
|
|
40
|
+
// => "Hello, David Bowie!"
|
|
28
41
|
```
|
|
29
|
-
|
|
30
|
-
|
|
42
|
+
|
|
43
|
+
## Usage
|
|
44
|
+
|
|
45
|
+
### String Substitution
|
|
46
|
+
|
|
47
|
+
Replace patterns with a fixed string value. Content between the start and end markers is ignored:
|
|
48
|
+
|
|
49
|
+
```javascript
|
|
50
|
+
const precedent = new Precedent();
|
|
51
|
+
|
|
52
|
+
precedent.addPattern('{Name', '}', 'David Bowie');
|
|
53
|
+
|
|
54
|
+
precedent.parseString('A message for {Name}.');
|
|
55
|
+
// => "A message for David Bowie."
|
|
56
|
+
|
|
57
|
+
// Content between markers is ignored for string substitutions
|
|
58
|
+
precedent.parseString('A message for {Name IGNORED TEXT}.');
|
|
59
|
+
// => "A message for David Bowie."
|
|
31
60
|
```
|
|
32
61
|
|
|
33
|
-
|
|
62
|
+
### Function-Based Parsing
|
|
63
|
+
|
|
64
|
+
Pass a function as the parser to dynamically process the content between markers:
|
|
65
|
+
|
|
66
|
+
```javascript
|
|
67
|
+
const precedent = new Precedent();
|
|
34
68
|
|
|
35
|
-
|
|
69
|
+
precedent.addPattern('{Length', '}', (pString) => { return pString.length; });
|
|
36
70
|
|
|
37
|
-
|
|
71
|
+
precedent.parseString('The length is {Length some text}.');
|
|
72
|
+
// => "The length is some text." (length of " some text")
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Passing Data to Parsers
|
|
76
|
+
|
|
77
|
+
A data object can be passed as the second argument to `parseString`, which is then available to parser functions:
|
|
38
78
|
|
|
39
79
|
```javascript
|
|
40
|
-
|
|
41
|
-
libPrecedent.addPattern('{Name', '}', 'David Bowie');
|
|
80
|
+
const precedent = new Precedent();
|
|
42
81
|
|
|
43
|
-
|
|
44
|
-
|
|
82
|
+
precedent.addPattern('<%=', '%>', (pContent, pData) =>
|
|
83
|
+
{
|
|
84
|
+
return pData[pContent.trim()] || '';
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
precedent.parseString('Hello, <%= username %>!', { username: 'Steven' });
|
|
88
|
+
// => "Hello, Steven!"
|
|
45
89
|
```
|
|
46
90
|
|
|
47
|
-
|
|
91
|
+
## API
|
|
48
92
|
|
|
49
|
-
|
|
50
|
-
Type: `String`
|
|
93
|
+
### `addPattern(patternStart, patternEnd, parser)`
|
|
51
94
|
|
|
52
|
-
|
|
95
|
+
Add a pattern to the parse tree.
|
|
53
96
|
|
|
54
|
-
|
|
55
|
-
|
|
97
|
+
| Parameter | Type | Description |
|
|
98
|
+
|-----------|------|-------------|
|
|
99
|
+
| `patternStart` | `String` | The opening marker for the pattern |
|
|
100
|
+
| `patternEnd` | `String` | The closing marker for the pattern |
|
|
101
|
+
| `parser` | `String` or `Function` | Replacement string, or function receiving `(content, data)` |
|
|
56
102
|
|
|
57
|
-
|
|
103
|
+
Returns `true` if the pattern was added successfully.
|
|
58
104
|
|
|
59
|
-
|
|
60
|
-
Type: `String` or `Function`
|
|
61
|
-
Default: Echo content between the pattern start and end.
|
|
105
|
+
### `parseString(contentString, data)`
|
|
62
106
|
|
|
63
|
-
|
|
107
|
+
Parse a string against all registered patterns.
|
|
64
108
|
|
|
65
|
-
|
|
109
|
+
| Parameter | Type | Description |
|
|
110
|
+
|-----------|------|-------------|
|
|
111
|
+
| `contentString` | `String` | The text to parse |
|
|
112
|
+
| `data` | `Object` | Optional data object passed to parser functions |
|
|
66
113
|
|
|
67
|
-
|
|
114
|
+
Returns the parsed string.
|
|
68
115
|
|
|
69
|
-
|
|
70
|
-
|
|
116
|
+
## Part of the Retold Framework
|
|
117
|
+
|
|
118
|
+
Precedent is used throughout the Fable ecosystem for template processing:
|
|
119
|
+
|
|
120
|
+
- [fable](https://github.com/stevenvelozo/fable) - Application services framework
|
|
121
|
+
- [pict](https://github.com/stevenvelozo/pict) - UI framework
|
|
122
|
+
- [pict-template](https://github.com/stevenvelozo/pict-template) - Template engine built on Precedent
|
|
123
|
+
|
|
124
|
+
## Testing
|
|
125
|
+
|
|
126
|
+
Run the test suite:
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
npm test
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Run with coverage:
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
npm run coverage
|
|
71
136
|
```
|
|
72
137
|
|
|
73
|
-
|
|
74
|
-
|
|
138
|
+
## Related Packages
|
|
139
|
+
|
|
140
|
+
- [fable](https://github.com/stevenvelozo/fable) - Application services framework
|
|
141
|
+
- [pict-template](https://github.com/stevenvelozo/pict-template) - Template engine
|
|
142
|
+
|
|
143
|
+
## License
|
|
144
|
+
|
|
145
|
+
MIT
|
|
146
|
+
|
|
147
|
+
## Contributing
|
|
75
148
|
|
|
76
|
-
|
|
149
|
+
Pull requests are welcome. For details on our code of conduct, contribution process, and testing requirements, see the [Retold Contributing Guide](https://github.com/stevenvelozo/retold/blob/main/docs/contributing.md).
|
package/docs/.nojekyll
ADDED
|
File without changes
|
package/docs/README.md
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# Precedent
|
|
2
|
+
|
|
3
|
+
A meta-templating engine that parses text strings and replaces delimited regions with custom output. Register start/end delimiter pairs, attach a handler function or replacement string, then parse any text through the engine.
|
|
4
|
+
|
|
5
|
+
Precedent is the foundation beneath Pict's `{~...~}` template expression system and Fable Settings' `${...}` environment variable substitution. It has zero runtime dependencies and works in both Node.js and the browser.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install precedent
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Quick Start
|
|
14
|
+
|
|
15
|
+
```javascript
|
|
16
|
+
const libPrecedent = require('precedent');
|
|
17
|
+
let processor = new libPrecedent();
|
|
18
|
+
|
|
19
|
+
// Register a pattern: replace anything between << and >> with "REDACTED"
|
|
20
|
+
processor.addPattern('<<', '>>', 'REDACTED');
|
|
21
|
+
|
|
22
|
+
let result = processor.parseString('The code is <<SECRET123>> end.');
|
|
23
|
+
// => "The code is REDACTED end."
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Core Concepts
|
|
27
|
+
|
|
28
|
+
### 1. Patterns
|
|
29
|
+
|
|
30
|
+
A pattern is a pair of delimiter strings (start and end) plus a handler. When the parser encounters the start delimiter in a string, it captures everything until the end delimiter, then calls the handler.
|
|
31
|
+
|
|
32
|
+
```javascript
|
|
33
|
+
processor.addPattern(startDelimiter, endDelimiter, handler);
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### 2. Handlers
|
|
37
|
+
|
|
38
|
+
The handler can be:
|
|
39
|
+
|
|
40
|
+
| Type | Behavior |
|
|
41
|
+
|------|----------|
|
|
42
|
+
| **string** | The matched region is replaced with this string |
|
|
43
|
+
| **function** | Called with `(content, data)` — content between delimiters and the data argument from `parseString()` |
|
|
44
|
+
| **omitted** | The content between delimiters is passed through unchanged |
|
|
45
|
+
|
|
46
|
+
### 3. Data Passing
|
|
47
|
+
|
|
48
|
+
The second argument to `parseString()` is passed to every handler function as its second parameter:
|
|
49
|
+
|
|
50
|
+
```javascript
|
|
51
|
+
processor.addPattern('{', '}',
|
|
52
|
+
(pContent, pData) =>
|
|
53
|
+
{
|
|
54
|
+
return pData[pContent];
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
let result = processor.parseString('Hello, {name}!', { name: 'Alice' });
|
|
58
|
+
// => "Hello, Alice!"
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### 4. Pattern Precedence
|
|
62
|
+
|
|
63
|
+
Patterns are stored in a character-by-character word tree (directed graph). When multiple patterns share a prefix, the longest matching start delimiter wins:
|
|
64
|
+
|
|
65
|
+
```javascript
|
|
66
|
+
processor.addPattern('<', '>', 'SHORT');
|
|
67
|
+
processor.addPattern('<<', '>', 'MEDIUM');
|
|
68
|
+
processor.addPattern('<<LONG', '>', 'LONG');
|
|
69
|
+
|
|
70
|
+
processor.parseString('<x>'); // => "SHORT"
|
|
71
|
+
processor.parseString('<<x>'); // => "MEDIUM"
|
|
72
|
+
processor.parseString('<<LONGx>'); // => "LONG"
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
If a longer match starts but fails to complete, the parser falls back to the shorter match.
|
|
76
|
+
|
|
77
|
+
## How It Works
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
addPattern('${', '}', fn)
|
|
81
|
+
│
|
|
82
|
+
▼
|
|
83
|
+
┌──────────────────────────────────┐
|
|
84
|
+
│ WordTree │
|
|
85
|
+
│ │
|
|
86
|
+
│ Builds a character-by-character │
|
|
87
|
+
│ tree from start delimiters. │
|
|
88
|
+
│ End delimiters are stored as │
|
|
89
|
+
│ subtrees on the terminal node. │
|
|
90
|
+
│ │
|
|
91
|
+
│ $ ──▶ { ──▶ PatternEnd ──▶ } │
|
|
92
|
+
│ └─ Parse() │
|
|
93
|
+
└──────────────────────────────────┘
|
|
94
|
+
│
|
|
95
|
+
▼
|
|
96
|
+
┌──────────────────────────────────┐
|
|
97
|
+
│ StringParser │
|
|
98
|
+
│ │
|
|
99
|
+
│ Scans character by character: │
|
|
100
|
+
│ 1. Match start delimiter chars │
|
|
101
|
+
│ 2. Buffer content │
|
|
102
|
+
│ 3. Match end delimiter chars │
|
|
103
|
+
│ 4. Call Parse(content, data) │
|
|
104
|
+
│ 5. Replace matched region │
|
|
105
|
+
└──────────────────────────────────┘
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
The parser is stateful and stream-oriented — it processes one character at a time, tracking whether it is in raw mode, matching a start delimiter, buffering content, or matching an end delimiter.
|
|
109
|
+
|
|
110
|
+
## Architecture
|
|
111
|
+
|
|
112
|
+
Precedent consists of three classes:
|
|
113
|
+
|
|
114
|
+
| Class | Responsibility |
|
|
115
|
+
|-------|---------------|
|
|
116
|
+
| **Precedent** | Public facade — exposes `addPattern()` and `parseString()` |
|
|
117
|
+
| **WordTree** | Stores delimiter patterns in a character-based tree structure |
|
|
118
|
+
| **StringParser** | Character-by-character parser that traverses the tree and invokes handlers |
|
|
119
|
+
|
|
120
|
+
## Browser Usage
|
|
121
|
+
|
|
122
|
+
The browser shim (`Precedent-Browser-Shim.js`) assigns the constructor to `window.Precedent`:
|
|
123
|
+
|
|
124
|
+
```html
|
|
125
|
+
<script src="precedent.min.js"></script>
|
|
126
|
+
<script>
|
|
127
|
+
var processor = new Precedent();
|
|
128
|
+
processor.addPattern('{{', '}}', function(content) { return content.toUpperCase(); });
|
|
129
|
+
document.body.innerHTML = processor.parseString('Hello {{world}}');
|
|
130
|
+
// => "Hello WORLD"
|
|
131
|
+
</script>
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Build with Quackage: `npx quack build`
|
|
135
|
+
|
|
136
|
+
## Where Precedent Is Used
|
|
137
|
+
|
|
138
|
+
| Consumer | Pattern | Purpose |
|
|
139
|
+
|----------|---------|---------|
|
|
140
|
+
| **Fable Settings** | `${VAR\|default}` | Environment variable substitution in configuration |
|
|
141
|
+
| **Pict** | `{~Prefix:content~}` | 40+ template expression types for data, logic, rendering |
|
|
142
|
+
| **Pict Template** | Custom patterns | Base class for all Pict template expressions |
|
package/docs/_sidebar.md
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
- Getting Started
|
|
2
|
+
|
|
3
|
+
- [Overview](README.md)
|
|
4
|
+
|
|
5
|
+
- Reference
|
|
6
|
+
|
|
7
|
+
- [API Reference](api.md)
|
|
8
|
+
|
|
9
|
+
- Examples
|
|
10
|
+
|
|
11
|
+
- [Usage Patterns](examples.md)
|
|
12
|
+
|
|
13
|
+
- Retold Ecosystem
|
|
14
|
+
|
|
15
|
+
- [Pict](/pict/pict/)
|
|
16
|
+
- [Pict Template](/pict/pict-template/)
|
|
17
|
+
- [Fable](/fable/fable/)
|
|
18
|
+
- [Fable Settings](/fable/fable-settings/)
|
|
19
|
+
- [Indoctrinate](/utility/indoctrinate/)
|
package/docs/_topbar.md
ADDED
package/docs/api.md
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
# API Reference
|
|
2
|
+
|
|
3
|
+
## Class: Precedent
|
|
4
|
+
|
|
5
|
+
The main public interface. Creates a WordTree for pattern storage and a StringParser for execution.
|
|
6
|
+
|
|
7
|
+
### Constructor
|
|
8
|
+
|
|
9
|
+
```javascript
|
|
10
|
+
const libPrecedent = require('precedent');
|
|
11
|
+
let processor = new libPrecedent();
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
No parameters. The constructor initializes an empty parse tree.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Properties
|
|
19
|
+
|
|
20
|
+
### ParseTree
|
|
21
|
+
|
|
22
|
+
The root node of the internal word tree. This is the directed graph that stores all registered patterns as character-by-character paths.
|
|
23
|
+
|
|
24
|
+
**Type:** `object`
|
|
25
|
+
|
|
26
|
+
Inspect it to see the current tree structure:
|
|
27
|
+
|
|
28
|
+
```javascript
|
|
29
|
+
processor.addPattern('${', '}', myHandler);
|
|
30
|
+
console.log(JSON.stringify(processor.ParseTree, null, 2));
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### WordTree
|
|
34
|
+
|
|
35
|
+
The WordTree instance that manages pattern storage.
|
|
36
|
+
|
|
37
|
+
**Type:** `WordTree`
|
|
38
|
+
|
|
39
|
+
### StringParser
|
|
40
|
+
|
|
41
|
+
The StringParser instance that executes pattern matching.
|
|
42
|
+
|
|
43
|
+
**Type:** `StringParser`
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Methods
|
|
48
|
+
|
|
49
|
+
### addPattern(pPatternStart, pPatternEnd, pParser)
|
|
50
|
+
|
|
51
|
+
Register a delimiter pair and a handler with the parse tree.
|
|
52
|
+
|
|
53
|
+
| Parameter | Type | Required | Description |
|
|
54
|
+
|-----------|------|----------|-------------|
|
|
55
|
+
| `pPatternStart` | string | Yes | The opening delimiter (e.g. `'${'`, `'<%'`, `'<'`) |
|
|
56
|
+
| `pPatternEnd` | string | No | The closing delimiter (e.g. `'}'`, `'%>'`, `'>'`). If omitted, defaults to `pPatternStart` |
|
|
57
|
+
| `pParser` | string \| function | No | The handler (see below) |
|
|
58
|
+
|
|
59
|
+
**Returns:** `boolean` — `true` if the pattern was added, `false` if the start or end delimiter was empty.
|
|
60
|
+
|
|
61
|
+
#### Handler Types
|
|
62
|
+
|
|
63
|
+
**String handler** — Replace the matched region with this literal string:
|
|
64
|
+
|
|
65
|
+
```javascript
|
|
66
|
+
processor.addPattern('<%', '%>', 'REPLACED');
|
|
67
|
+
processor.parseString('A <%stuff%> B');
|
|
68
|
+
// => "A REPLACED B"
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
**Function handler** — Called with two arguments:
|
|
72
|
+
|
|
73
|
+
| Argument | Description |
|
|
74
|
+
|----------|-------------|
|
|
75
|
+
| `pContent` | The text between the start and end delimiters (delimiters stripped) |
|
|
76
|
+
| `pData` | The second argument passed to `parseString()` |
|
|
77
|
+
|
|
78
|
+
```javascript
|
|
79
|
+
processor.addPattern('<%#', '%>',
|
|
80
|
+
(pContent, pData) =>
|
|
81
|
+
{
|
|
82
|
+
return pContent.length;
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
processor.parseString('Count: <%#ABCDE%>');
|
|
86
|
+
// => "Count: 5"
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
**No handler** — The content between delimiters is passed through (delimiters are stripped):
|
|
90
|
+
|
|
91
|
+
```javascript
|
|
92
|
+
processor.addPattern('$');
|
|
93
|
+
processor.parseString('A $comment$ B');
|
|
94
|
+
// => "A comment B"
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
#### Self-Closing Patterns
|
|
98
|
+
|
|
99
|
+
When only `pPatternStart` is provided (no end delimiter), the start string is used as both the opening and closing delimiter:
|
|
100
|
+
|
|
101
|
+
```javascript
|
|
102
|
+
processor.addPattern('$');
|
|
103
|
+
// Equivalent to: processor.addPattern('$', '$')
|
|
104
|
+
|
|
105
|
+
processor.parseString('Hello $World$ Goodbye');
|
|
106
|
+
// => "Hello World Goodbye"
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
### parseString(pString, pData)
|
|
112
|
+
|
|
113
|
+
Parse a string against all registered patterns, replacing matched regions with handler output.
|
|
114
|
+
|
|
115
|
+
| Parameter | Type | Required | Description |
|
|
116
|
+
|-----------|------|----------|-------------|
|
|
117
|
+
| `pString` | string | Yes | The text to parse |
|
|
118
|
+
| `pData` | any | No | Passed as the second argument to every function handler |
|
|
119
|
+
|
|
120
|
+
**Returns:** `string` — The processed string with all matched patterns replaced.
|
|
121
|
+
|
|
122
|
+
```javascript
|
|
123
|
+
processor.addPattern('{', '}',
|
|
124
|
+
(pContent, pData) =>
|
|
125
|
+
{
|
|
126
|
+
return pData[pContent] || pContent;
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
let result = processor.parseString(
|
|
130
|
+
'Hello {name}, welcome to {place}.',
|
|
131
|
+
{ name: 'Alice', place: 'Wonderland' }
|
|
132
|
+
);
|
|
133
|
+
// => "Hello Alice, welcome to Wonderland."
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
If no patterns match, the input string is returned unchanged.
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## Class: WordTree
|
|
141
|
+
|
|
142
|
+
Builds and maintains the internal directed graph for pattern matching. You rarely interact with this directly — `Precedent.addPattern()` delegates to it.
|
|
143
|
+
|
|
144
|
+
### addPattern(pPatternStart, pPatternEnd, fParser)
|
|
145
|
+
|
|
146
|
+
Adds a pattern to the tree. Each character of the start delimiter becomes a node. The end delimiter is stored as a subtree (`PatternEnd`) on the terminal start node. The handler function is stored on the terminal end node as `Parse`.
|
|
147
|
+
|
|
148
|
+
**Returns:** `boolean`
|
|
149
|
+
|
|
150
|
+
### Tree Structure
|
|
151
|
+
|
|
152
|
+
For `addPattern('${', '}', fn)`, the tree looks like:
|
|
153
|
+
|
|
154
|
+
```
|
|
155
|
+
ParseTree
|
|
156
|
+
└── '$'
|
|
157
|
+
└── '{'
|
|
158
|
+
└── PatternEnd
|
|
159
|
+
└── '}'
|
|
160
|
+
├── PatternStartString: '${'
|
|
161
|
+
├── PatternEndString: '}'
|
|
162
|
+
└── Parse: fn
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Multiple patterns sharing a prefix character (e.g. `<`, `<<`, `<<LONG`) branch naturally within the tree, enabling longest-match-first behavior.
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## Class: StringParser
|
|
170
|
+
|
|
171
|
+
Character-by-character parser that traverses the WordTree and executes handler functions. You rarely interact with this directly — `Precedent.parseString()` delegates to it.
|
|
172
|
+
|
|
173
|
+
### parseString(pString, pParseTree, pData)
|
|
174
|
+
|
|
175
|
+
Scans the input string one character at a time, maintaining a state object that tracks:
|
|
176
|
+
|
|
177
|
+
| State Property | Purpose |
|
|
178
|
+
|---------------|---------|
|
|
179
|
+
| `Output` | Accumulated final output |
|
|
180
|
+
| `OutputBuffer` | Characters being buffered during a potential match |
|
|
181
|
+
| `Pattern` | Current node in the word tree |
|
|
182
|
+
| `PatternMatch` | Whether we are currently in a pattern match |
|
|
183
|
+
| `StartPatternMatchComplete` | Whether the full start delimiter has been matched |
|
|
184
|
+
| `EndPatternMatchBegan` | Whether end delimiter matching has started |
|
|
185
|
+
|
|
186
|
+
### Parsing Flow
|
|
187
|
+
|
|
188
|
+
1. **Raw mode** — Characters are buffered directly to output
|
|
189
|
+
2. **Start match** — A character matches the tree root; parser begins traversing the tree
|
|
190
|
+
3. **Content capture** — Start delimiter fully matched; characters are buffered as content
|
|
191
|
+
4. **End match** — End delimiter characters begin matching the PatternEnd subtree
|
|
192
|
+
5. **Execution** — End delimiter fully matched; handler is called with `(content, data)`
|
|
193
|
+
6. **Replacement** — Handler output replaces the entire matched region (delimiters + content)
|
|
194
|
+
7. **Reset** — Parser returns to raw mode
|
|
195
|
+
|
|
196
|
+
If a partial start match fails (the next character does not continue the tree path), the buffered characters are flushed as-is and the parser resets to raw mode.
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
## Error Handling
|
|
201
|
+
|
|
202
|
+
| Condition | Behavior |
|
|
203
|
+
|-----------|----------|
|
|
204
|
+
| Empty start delimiter | `addPattern()` returns `false` |
|
|
205
|
+
| Empty end delimiter | `addPattern()` returns `false` |
|
|
206
|
+
| Empty input string | `parseString()` returns `''` |
|
|
207
|
+
| No matching patterns | Input is returned unchanged |
|
|
208
|
+
| Unmatched start delimiter | Start delimiter characters pass through as-is |
|
|
209
|
+
| Nested start delimiters | Inner start is treated as content, not a new match |
|
package/docs/cover.md
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Precedent
|
|
2
|
+
|
|
3
|
+
> A zero-dependency meta-templating engine for Node.js and the browser
|
|
4
|
+
|
|
5
|
+
Define arbitrary delimiter patterns and replace them with strings, computed values, or function output. Precedent powers the `{~...~}` expression system in Pict and the `${...}` environment variable substitution in Fable Settings.
|
|
6
|
+
|
|
7
|
+
- **Custom Delimiters** -- Register any start/end string pair as a pattern with `addPattern()`
|
|
8
|
+
- **Function Handlers** -- Patterns can replace with static strings or call a function with the matched content and a data argument
|
|
9
|
+
- **Pattern Precedence** -- A word tree ensures longer delimiters match before shorter ones (`<<EXTRA` before `<<` before `<`)
|
|
10
|
+
- **Zero Dependencies** -- No runtime dependencies; under 300 lines of code
|
|
11
|
+
- **Browser Ready** -- Includes a browser shim that assigns `window.Precedent`; builds with Quackage
|
|
12
|
+
|
|
13
|
+
[Quick Start](README.md)
|
|
14
|
+
[API Reference](api.md)
|
|
15
|
+
[GitHub](https://github.com/stevenvelozo/precedent)
|