Skip to content

Commit c34034b

Browse files
committed
Allow hyphen in tag names
1 parent f6301c6 commit c34034b

File tree

9 files changed

+103
-24
lines changed

9 files changed

+103
-24
lines changed

src/lexer.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,15 @@ const isAllowedAlpha = (code: number) => {
3535
(code >= 48 && code <= 57) || // 0-9
3636
(code >= 65 && code <= 90) || // A-Z
3737
(code >= 97 && code <= 122) || // a-z
38-
code === 95 || // _
38+
code === 45 || // - hyphen
39+
code === 95 || // _ underscore
3940
(code >= 192 && code <= 255) || // À-Ÿ à-ÿ
4041
(code >= 940 && code <= 974) // ά-ω
4142
);
4243
};
44+
const isValidName = (text: string) => {
45+
return text.endsWith('-') === false && text.endsWith('_') === false;
46+
};
4347
const MAX_NAME_LEN = 42;
4448

4549
/**
@@ -251,17 +255,17 @@ export default class Lexer {
251255
pending.rawText += char;
252256
pending.name += char;
253257
} // Is this a space after the tag name?
254-
else if (isSpace(char)) {
258+
else if (isSpace(char) && isValidName(pending.name)) {
255259
pending.rawText += char;
256260
transition(STATE_INSIDE_TAG);
257261
} // Is this a tag stopper?
258262
// In this case, it's a single tag
259-
else if (char === lastStopperChar) {
263+
else if (char === lastStopperChar && isValidName(pending.name)) {
260264
pending.rawText += char;
261265
pending.single = true;
262266
transition(STATE_CLOSE_TAG);
263267
} // Is this the end of the First tag from a Double tag?
264-
else if (char === closeTagChar) {
268+
else if (char === closeTagChar && isValidName(pending.name)) {
265269
pending.rawText += char;
266270
pending.double = true;
267271
commitAndTransition(STATE_RAW_TEXT);
@@ -320,7 +324,7 @@ export default class Lexer {
320324
pending.param_key += char;
321325
} // Is this the equal between key and value?
322326
// Only "=" allowed between param & value
323-
else if (char === '=') {
327+
else if (char === '=' && isValidName(pending.param_key)) {
324328
pending.rawText += char;
325329
transition(STATE_EQUAL);
326330
} else {

src/parser.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import { type DoubleTag, type ParseToken } from './types.ts';
55
import Lexer from './lexer.ts';
66
import { log } from './logger.ts';
77

8+
const LETTERS = 'a-zàáâãäæçèéêëìíîïñòóôõöùúûüýÿœάαβγδεζηθικλμνξοπρστυφχψω';
9+
810
/**
911
* AST (Abstract Syntax Tree) class for parsing text into a structured format.
1012
*/
@@ -72,12 +74,9 @@ export default class AST {
7274
}
7375

7476
const { openTag, lastStopper } = this.config;
75-
const RE_FIRST_START = new RegExp(
76-
`^[${openTag as string}][ ]*[a-zàáâãäæçèéêëìíîïñòóôõöùúûüýÿœάαβγδεζηθικλμνξοπρστυφχψω]`
77-
);
78-
const RE_SECOND_START = new RegExp(
79-
`^[${openTag as string}][${lastStopper as string}][ ]*[a-zàáâãäæçèéêëìíîïñòóôõöùúûüýÿœάαβγδεζηθικλμνξοπρστυφχψω]`
80-
);
77+
// These regexes have to be calculated in place, because the config is dynamic
78+
const RE_FIRST_START = new RegExp(`^[${openTag as string}][ ]*[${LETTERS}]`);
79+
const RE_SECOND_START = new RegExp(`^[${openTag as string}][${lastStopper as string}][ ]*[${LETTERS}]`);
8180

8281
const tree: ParseToken[] = [];
8382
const stack: ParseToken[] = [];

src/runtime.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,11 @@ export default class Runtime {
5353
static fromText(
5454
text: string,
5555
customTags: Record<string, Function> = {},
56-
cfg: T.ConfigFull = config.defaultCfg
56+
cfg: T.ConfigFull = config.defaultCfg,
57+
memoCache: MemoCache = new MemoCache()
5758
): Runtime {
5859
const runtime = new Runtime(customTags, cfg);
60+
runtime.memoCache = memoCache;
5961
runtime.file = {
6062
size: text.length,
6163
hash: crypto.createHash('sha224').update(text).digest('hex'),
@@ -71,9 +73,11 @@ export default class Runtime {
7173
static async fromFile(
7274
file: string | T.RuntimeFile,
7375
customTags: Record<string, Function> = {},
74-
cfg: T.ConfigFull = config.defaultCfg
76+
cfg: T.ConfigFull = config.defaultCfg,
77+
memoCache: MemoCache = new MemoCache()
7578
): Promise<Runtime> {
7679
const runtime = new Runtime(customTags, cfg);
80+
runtime.memoCache = memoCache;
7781
const fname: string = typeof file === 'string' ? path.resolve(file) : file.fname!;
7882
let dname = '';
7983
if (typeof file === 'string') {

test/cache.test.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,20 @@ import { sleep } from '../src/util.ts';
66

77
// Test MemoCache class
88
//
9-
test('MemoCache basic set/get/has', () => {
9+
test('MemoCache basic set/get/has', async () => {
1010
const cache = new MemoCache();
11-
cache.setCache('foo', 123, 100);
11+
cache.setCache('foo', 123, 20);
1212
expect(cache.hasCache('foo')).toBe(true);
1313
expect(cache.getCache('foo')).toBe(123);
1414
expect(cache.hasCache('foo')).toBe(true); // still alive
15+
await sleep(20); // let timer run
1516
});
1617

1718
test('MemoCache TTL expiration', async () => {
1819
const cache = new MemoCache();
1920
cache.setCache('bar', 'baz', 10);
2021
expect(cache.hasCache('bar')).toBe(true);
21-
await sleep(15); // wait for expiration
22+
await sleep(11); // wait for expiration
2223
expect(cache.hasCache('bar')).toBe(false);
2324
expect(cache.getCache('bar')).toBeUndefined();
2425
});

test/fixtures/zoo.xml

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
<?xml version="1.0"?>
2+
<Park.Zoo>
3+
<lion>2</lion>
4+
<tiger type="Fixnum">3</tiger>
5+
<bear type="Float">5.7</bear>
6+
<oh-my type="TrueClass"/>
7+
<oh-my type="FalseClass"/>
8+
<snake type="Symbol">boa</snake>
9+
<big_guy type="Park.Animal">
10+
<type>Godzilla</type>
11+
<base type="Base64">PGJhc2U2ND4=</base>
12+
</big_guy>
13+
<nothing/>
14+
<tea_time type="Time">1288724706.500121</tea_time>
15+
<minutes type="Array">
16+
<i>0</i>
17+
<i>1</i>
18+
<i>5555</i>
19+
<i>75932</i>
20+
</minutes>
21+
<empty type="Array"/>
22+
<mixed type="Array">
23+
<nil/>
24+
<i>55</i>
25+
<f>5.6</f>
26+
<y/>
27+
<n/>
28+
<t>1288724706.500122</t>
29+
<s>silly</s>
30+
<m>symbol</m>
31+
</mixed>
32+
<clueless type="Hash"/>
33+
<clues type="Hash">
34+
<y/>
35+
<nil/>
36+
<i>1</i>
37+
<s>cat</s>
38+
<m>two</m>
39+
<t>1288724706.500123</t>
40+
</clues>
41+
<nest type="Array">
42+
<i>37</i>
43+
<a/>
44+
<a>
45+
<i>77</i>
46+
</a>
47+
<h/>
48+
<h>
49+
<m>cnt</m>
50+
<i>123</i>
51+
<i>37</i>
52+
<a>
53+
<i>77</i>
54+
</a>
55+
<i>88</i>
56+
<s>ate</s>
57+
<s>in</s>
58+
<h>
59+
<s>prime</s>
60+
<i>3</i>
61+
</h>
62+
</h>
63+
</nest>
64+
</Park.Zoo>

test/lexer.test.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,13 @@ const TESTS = [
1717
['<x 1 />', [{ index: 0, rawText: '<x 1 />' }]],
1818
['<A B />', [{ index: 0, rawText: '<A B />' }]],
1919
['<ha/ >', [{ index: 0, rawText: '<ha/ >' }]],
20+
['<-- />', [{ index: 0, rawText: '<-- />' }]],
21+
['<--/>', [{ index: 0, rawText: '<--/>' }]],
22+
['<-->', [{ index: 0, rawText: '<-->' }]],
23+
['<-a />', [{ index: 0, rawText: '<-a />' }]],
24+
['<a- />', [{ index: 0, rawText: '<a- />' }]],
25+
['<a-/>', [{ index: 0, rawText: '<a-/>' }]],
26+
['<a->', [{ index: 0, rawText: '<a->' }]],
2027
['<1tag />', [{ index: 0, rawText: '<1tag />' }]], // tag cannot start with Number
2128
['<tag X=0 />', [{ index: 0, rawText: '<tag X=0 />' }]], // prop cannot start with Upper
2229
['<tag 1=2 />', [{ index: 0, rawText: '<tag 1=2 />' }]], // prop cannot start with Number
@@ -72,6 +79,7 @@ const TESTS = [
7279
['<x >', [{ index: 0, rawText: '<x >', name: 'x', double: true }]],
7380
['<xY1/>', [{ index: 0, rawText: '<xY1/>', name: 'xY1', single: true }]],
7481
['< x/>', [{ index: 0, rawText: '< x/>', name: 'x', single: true }]],
82+
['<x-y/>', [{ index: 0, rawText: '<x-y/>', name: 'xY', single: true }]],
7583
['<x />', [{ index: 0, rawText: '<x />', name: 'x', single: true }]],
7684
['<x />', [{ index: 0, rawText: '<x />', name: 'x', single: true }]],
7785
[

test/render.test.ts

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,17 +31,15 @@ test('some blocks found', async () => {
3131
});
3232

3333
const lex = o.lex(txt);
34-
expect(lex).toHaveLength(14);
34+
expect(lex.length).toBeGreaterThan(10);
3535
expect(isRawText(lex[0])).toBeTruthy();
3636
const ast = new AST().parse(lex);
37-
expect(ast).toHaveLength(7);
37+
expect(ast).toHaveLength(9);
3838

3939
expect(isRawText(ast[0])).toBeTruthy();
40-
expect(isDoubleTag(ast[1]) && ast[1].name === 'open1').toBeTruthy();
41-
expect(isRawText(ast[2])).toBeTruthy();
42-
expect(isSingleTag(ast[3]) && ast[3].name === 'replaceWeather').toBeTruthy();
43-
expect(isRawText(ast[4])).toBeTruthy();
44-
expect(isDoubleTag(ast[5]) && ast[5].name === 'replaceSort').toBeTruthy();
40+
expect(isDoubleTag(ast[3]) && ast[3].name === 'open1').toBeTruthy();
41+
expect(isSingleTag(ast[5]) && ast[5].name === 'replaceWeather').toBeTruthy();
42+
expect(isDoubleTag(ast[7]) && ast[7].name === 'replaceSort').toBeTruthy();
4543
});
4644

4745
test('render file no tags', async () => {

test/scan.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ test('scan: no blocks found', async () => {
1717
test('scan: some blocks found', async () => {
1818
const { validTags, invalidTags } = await scanFile(DIR + '/fixtures/text1.md');
1919
expect(validTags).toBe(0);
20-
expect(invalidTags).toBe(3);
20+
expect(invalidTags).toBe(4);
2121
});
2222

2323
test('scan XML no tags', async () => {

test/util.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { testing } from "./wrap.ts";
44
const { test, expect } = await testing;
55

66
test('camel case', () => {
7+
expect(toCamelCase('a-boo--')).toBe('aBoo');
78
expect(toCamelCase('blah_blah')).toBe('blahBlah');
89
expect(toCamelCase('blah blah ')).toBe('blahBlah');
910
expect(toCamelCase(' blah-blah')).toBe('blahBlah');

0 commit comments

Comments
 (0)