Skip to content

Commit b365ca3

Browse files
committed
allow to escape tokens
1 parent 7f55670 commit b365ca3

File tree

4 files changed

+50
-13
lines changed

4 files changed

+50
-13
lines changed

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "messagepipe",
3-
"version": "0.2.0",
3+
"version": "0.2.1",
44
"description": "Formats message strings with number, date, plural, and select placeholders to create localized messages",
55
"main": "dist/index.js",
66
"module": "dist/index.esm.js",

src/parser.test.ts

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,20 +15,36 @@ test('returns string with message', () => {
1515
assert.equal(parser('Hello {name}!'), ['"Hello "', 'a.name', '"!"'])
1616
})
1717

18+
test('returns empty selector + pipe', () => {
19+
assert.equal(parser('{|random}'), ['""', 'b.random()', '""'])
20+
})
21+
22+
test('returns empty selector + pipe 2', () => {
23+
assert.equal(parser('{ | random, min: 5, max:10}'), ['""', 'b. random(void 0,{ min: 5, max:10})', '""'])
24+
})
25+
1826
test('returns string with message and pipe', () => {
19-
assert.equal(parser('Hello {name | capitalize}!'), ['"Hello "', 'b. capitalize(a.name )', '"!"'])
27+
assert.equal(parser('Hello {name | capitalize}!'), ['"Hello "', 'b. capitalize(a.name)', '"!"'])
2028
})
2129

2230
test('returns string with message, pipe and arguments', () => {
23-
assert.equal(parser('Hello {name | capitalize, letter:"b"}!'), ['"Hello "', 'b. capitalize(a.name ,{ letter:"b"})', '"!"'])
31+
assert.equal(parser('Hello {name | capitalize, letter:"b"}!'), ['"Hello "', 'b. capitalize(a.name,{ letter:"b"})', '"!"'])
2432
})
2533

2634
test('returns string with message, pipe and arguments (true)', () => {
27-
assert.equal(parser('Hello {name | capitalize, letter}!'), ['"Hello "', 'b. capitalize(a.name ,{ letter:true})', '"!"'])
35+
assert.equal(parser('Hello {name | capitalize, letter}!'), ['"Hello "', 'b. capitalize(a.name,{ letter:true})', '"!"'])
2836
})
2937

3038
test('returns nested message selector', () => {
31-
assert.equal(parser('Hello {agents.{index}.name | capitalize}!'), ['"Hello "', 'b. capitalize(a.agents[a.index].name )', '"!"'])
39+
assert.equal(parser('Hello {agents.{index}.name | capitalize}!'), ['"Hello "', 'b. capitalize(a.agents[a.index].name)', '"!"'])
40+
})
41+
42+
test('returns nested message selector ONLY', () => {
43+
assert.equal(parser('Hello {{index} | capitalize}!'), ['"Hello "', 'b. capitalize(a[a.index])', '"!"'])
44+
})
45+
46+
test('returns escaped selector', () => {
47+
assert.equal(parser('Hello \\{agents.{index}.name | capitalize\\}!'), ['"Hello {agents."', 'a.index', '".name | capitalize}!"'])
3248
})
3349

3450
test.run()

src/parser.ts

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ const tokenEnd = '}';
33
const tokenPipe = '|';
44
const tokenPipeArguments = ',';
55
const tokenPipeArgumentsValue = ':';
6+
const tokenEscape = '\\';
67

78
// "Hello {name} from the {planet | capitalize} {distance | number, unit:"lightyears"}"
89
// (a) => "Hello " + a.name + " from the " + capitalize(a.planet) + " " + number(a.distance, {unit:'lightyears'})
@@ -22,13 +23,14 @@ function reducePipe(acc: string, [pipeName, ...pipeProps]: [string, ...[string,
2223
+ acc
2324
+ (!pipeProps.length
2425
? ''
25-
: ',{' + pipeProps.map(mapPipeProps).join() + '}')
26+
: (acc.trim() ? '' : 'void 0') + ',{' + pipeProps.map(mapPipeProps).join() + '}')
2627
+ ')';
2728
}
2829

2930
export function parser(text: string): string[] {
3031
const output: string[] = [];
3132
let chunk = '';
33+
let previousToken = '';
3234

3335
// This is where message starts.
3436
// "Hello {name}!"
@@ -40,7 +42,12 @@ export function parser(text: string): string[] {
4042
let localPipeIndex = -1;
4143

4244
function end(index: number) {
43-
const selector = localSelector ? 'a.' + localSelector : '';
45+
const selectorTrim = localSelector.trim();
46+
const selector = selectorTrim
47+
? selectorTrim[0] === '['
48+
? 'a' + selectorTrim
49+
: 'a.' + selectorTrim
50+
: '';
4451
const output = localPipes.reduce(reducePipe, selector);
4552

4653
return {
@@ -51,6 +58,12 @@ export function parser(text: string): string[] {
5158

5259
for (let i = startIndex; i < text.length; i++) {
5360
const char = text[i];
61+
const escaped = previousToken === tokenEscape;
62+
previousToken = char;
63+
64+
if (char === tokenEscape) {
65+
continue;
66+
}
5467

5568
if (tokenPipe === char) {
5669
localPipeIndex += 1;
@@ -59,12 +72,14 @@ export function parser(text: string): string[] {
5972
}
6073

6174
// Handle message inside selector
62-
if (tokenStart === char && localPipeIndex === -1) {
75+
if (tokenStart === char && localPipeIndex === -1 && !escaped) {
6376
const messageData = messageStart(i + 1);
6477
i = messageData.index;
78+
6579
if (localSelector[localSelector.length - 1] === '.') {
6680
localSelector = localSelector.slice(0, -1);
6781
}
82+
6883
localSelector += '[' + messageData.output + ']';
6984
continue;
7085
}
@@ -89,7 +104,7 @@ export function parser(text: string): string[] {
89104
}
90105

91106
// Handle message inside arguments
92-
if (tokenStart === char) {
107+
if (tokenStart === char && !escaped) {
93108
const messageData = messageStart(i + 1);
94109
i = messageData.index;
95110
args[args.length - 1] += messageData.output;
@@ -101,7 +116,7 @@ export function parser(text: string): string[] {
101116
}
102117

103118
// Message starts is outside allowed zones
104-
if (tokenStart === char) {
119+
if (tokenStart === char && !escaped) {
105120
fail(char, i);
106121
}
107122

@@ -122,8 +137,14 @@ export function parser(text: string): string[] {
122137

123138
for (let i = 0; i < text.length; i++) {
124139
const char = text[i];
140+
const escaped = previousToken === tokenEscape;
141+
previousToken = char;
142+
143+
if (char === tokenEscape) {
144+
continue;
145+
}
125146

126-
if (tokenStart === char) {
147+
if (tokenStart === char && !escaped) {
127148
output.push(JSON.stringify(chunk));
128149
chunk = '';
129150
const messageData = messageStart(i + 1);

0 commit comments

Comments
 (0)