@prostojs/rewrite 0.0.4 → 0.1.1
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/dist/index.cjs +3985 -0
- package/dist/{rewrite.d.ts → index.d.ts} +125 -147
- package/dist/index.mjs +3978 -0
- package/package.json +40 -43
- package/dist/rewrite.cjs.prod.js +0 -848
- package/dist/rewrite.esm-browser.prod.js +0 -1
- package/dist/rewrite.esm-bundler.js +0 -729
- package/dist/rewrite.global.prod.js +0 -1
- package/dist/src/__test__/html-parser.spec.d.ts +0 -2
- package/dist/src/__test__/html-parser.spec.d.ts.map +0 -1
- package/dist/src/__test__/mixed-parser.spec.d.ts +0 -2
- package/dist/src/__test__/mixed-parser.spec.d.ts.map +0 -1
- package/dist/src/__test__/rewriter.spec.d.ts +0 -2
- package/dist/src/__test__/rewriter.spec.d.ts.map +0 -1
- package/dist/src/__test__/strings.spec.d.ts +0 -2
- package/dist/src/__test__/strings.spec.d.ts.map +0 -1
- package/dist/src/__test__/text-parser.spec.d.ts +0 -2
- package/dist/src/__test__/text-parser.spec.d.ts.map +0 -1
- package/dist/src/constants.d.ts +0 -3
- package/dist/src/constants.d.ts.map +0 -1
- package/dist/src/files.d.ts +0 -8
- package/dist/src/files.d.ts.map +0 -1
- package/dist/src/index.d.ts +0 -18
- package/dist/src/index.d.ts.map +0 -1
- package/dist/src/rw-common.d.ts +0 -13
- package/dist/src/rw-common.d.ts.map +0 -1
- package/dist/src/rw-html-parser.d.ts +0 -17
- package/dist/src/rw-html-parser.d.ts.map +0 -1
- package/dist/src/rw-text-parser.d.ts +0 -31
- package/dist/src/rw-text-parser.d.ts.map +0 -1
- package/dist/src/string-expression.d.ts +0 -4
- package/dist/src/string-expression.d.ts.map +0 -1
- package/dist/src/types.d.ts +0 -96
- package/dist/src/types.d.ts.map +0 -1
- package/dist/src/utils.d.ts +0 -3
- package/dist/src/utils.d.ts.map +0 -1
- package/index.js +0 -2
|
@@ -1,729 +0,0 @@
|
|
|
1
|
-
import { BasicNode, renderCodeFragment } from '@prostojs/parser';
|
|
2
|
-
|
|
3
|
-
const htmlVoidTags = [
|
|
4
|
-
'area',
|
|
5
|
-
'base',
|
|
6
|
-
'br',
|
|
7
|
-
'col',
|
|
8
|
-
'command',
|
|
9
|
-
'embed',
|
|
10
|
-
'hr',
|
|
11
|
-
'img',
|
|
12
|
-
'input',
|
|
13
|
-
'keygen',
|
|
14
|
-
'link',
|
|
15
|
-
'meta',
|
|
16
|
-
'param',
|
|
17
|
-
'source',
|
|
18
|
-
'track',
|
|
19
|
-
'wbr',
|
|
20
|
-
];
|
|
21
|
-
const htmlTextTags = [
|
|
22
|
-
'script',
|
|
23
|
-
'style',
|
|
24
|
-
];
|
|
25
|
-
|
|
26
|
-
const errorMessage = 'Work with files supported only in NodeJS environment.';
|
|
27
|
-
async function readFile(filePath) {
|
|
28
|
-
throw new Error(errorMessage);
|
|
29
|
-
}
|
|
30
|
-
async function writeFile(targetPath, data) {
|
|
31
|
-
throw new Error(errorMessage);
|
|
32
|
-
}
|
|
33
|
-
async function copyFile(filePath, targetPath) {
|
|
34
|
-
throw new Error(errorMessage);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const stringNodeOptions = {
|
|
38
|
-
label: '',
|
|
39
|
-
icon: '"',
|
|
40
|
-
tokens: [/(?<quote>["'`])/, ({ customData }) => customData.quote || ''],
|
|
41
|
-
backSlash: '-ignore',
|
|
42
|
-
};
|
|
43
|
-
const stringNode = new BasicNode(stringNodeOptions);
|
|
44
|
-
function stringExpressionNodeFactory(interpolationDelimiters) {
|
|
45
|
-
return new BasicNode({
|
|
46
|
-
label: 'string',
|
|
47
|
-
icon: '≈',
|
|
48
|
-
tokens: [interpolationDelimiters[0], interpolationDelimiters[1]],
|
|
49
|
-
tokenOE: 'omit-omit',
|
|
50
|
-
})
|
|
51
|
-
.addAbsorbs(stringNode, 'join')
|
|
52
|
-
.mapContent('expression', 'join-clear')
|
|
53
|
-
.onPop(({ parserContext, customData: { expression } }) => {
|
|
54
|
-
try {
|
|
55
|
-
new Function(expression);
|
|
56
|
-
}
|
|
57
|
-
catch (e) {
|
|
58
|
-
parserContext.panic('Invalid expression: ' + e.message, expression.length + interpolationDelimiters[0].length + interpolationDelimiters[1].length);
|
|
59
|
-
}
|
|
60
|
-
})
|
|
61
|
-
.initCustomData(() => ({
|
|
62
|
-
expression: '',
|
|
63
|
-
code: ({ customData }, level = 0) => ' '.repeat(level * 2) + `__ += ${customData.expression.trim()}\n`,
|
|
64
|
-
}));
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function escapeRegex(s) {
|
|
68
|
-
return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
|
|
69
|
-
}
|
|
70
|
-
function debug(message) {
|
|
71
|
-
console.log('[33m' + '[1m' + '[2m' + '[rewrite] DEBUG: ' + message + '[0m');
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
function genSafeFunc(code) {
|
|
75
|
-
try {
|
|
76
|
-
return new Function('__ctx__', 'process', 'window', 'global', 'require', code);
|
|
77
|
-
}
|
|
78
|
-
catch (e) {
|
|
79
|
-
console.error(e.message);
|
|
80
|
-
console.error(e.stack);
|
|
81
|
-
throw e;
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
function escapeString(s) { return s.replace(/\\/g, '\\\\').replace(/`/g, '\\`').replace(/\n/g, '\\n').replace(/\$/g, '\\$'); }
|
|
85
|
-
function pushString(s) {
|
|
86
|
-
return `__ += \`${escapeString(s)}\`\n`;
|
|
87
|
-
}
|
|
88
|
-
function renderCode(context, level = 1) {
|
|
89
|
-
let s = '';
|
|
90
|
-
let result = '';
|
|
91
|
-
function renderS() {
|
|
92
|
-
if (s) {
|
|
93
|
-
result += indent + pushString(s);
|
|
94
|
-
s = '';
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
const indent = ' '.repeat(level * 2);
|
|
98
|
-
context.content.forEach(c => {
|
|
99
|
-
if (typeof c === 'string') {
|
|
100
|
-
s += c;
|
|
101
|
-
}
|
|
102
|
-
else {
|
|
103
|
-
renderS();
|
|
104
|
-
const data = c.getCustomData();
|
|
105
|
-
if (data.openCode) {
|
|
106
|
-
result += indent + data.openCode;
|
|
107
|
-
}
|
|
108
|
-
if (data.code) {
|
|
109
|
-
result += data.code(c, level);
|
|
110
|
-
}
|
|
111
|
-
result += renderCode(c, level + (data.openCode ? 1 : 0));
|
|
112
|
-
if (data.closeCode) {
|
|
113
|
-
result += indent + data.closeCode;
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
});
|
|
117
|
-
renderS();
|
|
118
|
-
return result;
|
|
119
|
-
}
|
|
120
|
-
function getRewriter(rootNode, debug$1 = false) {
|
|
121
|
-
function getCode(source) {
|
|
122
|
-
const result = rootNode.parse(source);
|
|
123
|
-
if (debug$1) {
|
|
124
|
-
debug('Parsed tree view:\n' + result.toTree());
|
|
125
|
-
}
|
|
126
|
-
return 'let __ = \'\'\n' +
|
|
127
|
-
'let __v = \'\'\n' +
|
|
128
|
-
'with (__ctx__) {\n' +
|
|
129
|
-
renderCode(result) +
|
|
130
|
-
'}\n' +
|
|
131
|
-
'return __';
|
|
132
|
-
}
|
|
133
|
-
function getFunc(source) {
|
|
134
|
-
const code = getCode(source);
|
|
135
|
-
const func = genSafeFunc(code);
|
|
136
|
-
return (context) => {
|
|
137
|
-
try {
|
|
138
|
-
return func(context);
|
|
139
|
-
}
|
|
140
|
-
catch (e) {
|
|
141
|
-
throwErrorFromFunction(e, code);
|
|
142
|
-
}
|
|
143
|
-
};
|
|
144
|
-
}
|
|
145
|
-
function print(source) {
|
|
146
|
-
return console.log(rootNode.parse(source).toTree());
|
|
147
|
-
}
|
|
148
|
-
return {
|
|
149
|
-
genRewriteCode: getCode,
|
|
150
|
-
genRewriteFunction: getFunc,
|
|
151
|
-
printAsTree: print,
|
|
152
|
-
rewrite: (source, context) => getFunc(source)(context || {}),
|
|
153
|
-
};
|
|
154
|
-
}
|
|
155
|
-
function throwErrorFromFunction(e, code) {
|
|
156
|
-
var _a;
|
|
157
|
-
const relevantLine = ((_a = e.stack) === null || _a === void 0 ? void 0 : _a.split('\n')[1]) || '';
|
|
158
|
-
const regex = /<anonymous>:(\d+):(\d+)\)/g;
|
|
159
|
-
const match = regex.exec(relevantLine);
|
|
160
|
-
if (match) {
|
|
161
|
-
const row = parseInt(match[1], 10) - 2;
|
|
162
|
-
const col = parseInt(match[2], 10) - 1;
|
|
163
|
-
const output = renderCodeFragment(code.split('\n'), {
|
|
164
|
-
row,
|
|
165
|
-
error: col,
|
|
166
|
-
});
|
|
167
|
-
console.error('[91m' + e.message + '[39m');
|
|
168
|
-
console.error(output);
|
|
169
|
-
}
|
|
170
|
-
throw e;
|
|
171
|
-
}
|
|
172
|
-
function getStringExpressionRewriter(interpolationDelimiters = ['{{', '}}']) {
|
|
173
|
-
const stringExpression = stringExpressionNodeFactory(interpolationDelimiters);
|
|
174
|
-
const rootNode = new BasicNode({}).addRecognizes(stringExpression);
|
|
175
|
-
function getCode(source) {
|
|
176
|
-
const result = rootNode.parse(source);
|
|
177
|
-
return 'let __ = []\n' +
|
|
178
|
-
'with (__ctx__) {\n' +
|
|
179
|
-
result.content.map(c => `__.push(${typeof c === 'string'
|
|
180
|
-
? `\`${escapeString(c)}\``
|
|
181
|
-
: `${c.getCustomData().expression}`})`).join('\n ') +
|
|
182
|
-
'}\n' +
|
|
183
|
-
'return __.length === 1 ? __[0] : __.join(\'\')';
|
|
184
|
-
}
|
|
185
|
-
function getFunc(source) {
|
|
186
|
-
const code = getCode(source);
|
|
187
|
-
const func = genSafeFunc(code);
|
|
188
|
-
return (context) => {
|
|
189
|
-
try {
|
|
190
|
-
return func(context);
|
|
191
|
-
}
|
|
192
|
-
catch (e) {
|
|
193
|
-
throwErrorFromFunction(e, code);
|
|
194
|
-
}
|
|
195
|
-
};
|
|
196
|
-
}
|
|
197
|
-
function print(source) {
|
|
198
|
-
return console.log(rootNode.parse(source).toTree());
|
|
199
|
-
}
|
|
200
|
-
return {
|
|
201
|
-
genRewriteCode: getCode,
|
|
202
|
-
genRewriteFunction: getFunc,
|
|
203
|
-
printAsTree: print,
|
|
204
|
-
rewrite: (source, context) => getFunc(source)(context || {}),
|
|
205
|
-
};
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
const docTypeNodeOptions = {
|
|
209
|
-
label: 'Document Type',
|
|
210
|
-
tokens: ['<!DOCTYPE ', '>'],
|
|
211
|
-
};
|
|
212
|
-
const cDataNodeOptions = {
|
|
213
|
-
icon: '<![CDATA[',
|
|
214
|
-
tokens: ['<![CDATA[', ']]>'],
|
|
215
|
-
};
|
|
216
|
-
const commentNodeOptions = {
|
|
217
|
-
label: 'comment',
|
|
218
|
-
icon: '“',
|
|
219
|
-
tokens: ['<!--', '-->'],
|
|
220
|
-
};
|
|
221
|
-
const valueNodeOptions = {
|
|
222
|
-
label: 'value',
|
|
223
|
-
icon: '=',
|
|
224
|
-
tokens: [/=(?<quote>["'`])/, ({ customData }) => customData.quote || ''],
|
|
225
|
-
backSlash: '-ignore',
|
|
226
|
-
tokenOE: 'omit-omit',
|
|
227
|
-
};
|
|
228
|
-
const valueNode = new BasicNode(valueNodeOptions);
|
|
229
|
-
const unquotedValueNodeOptions = {
|
|
230
|
-
label: 'value',
|
|
231
|
-
icon: '=',
|
|
232
|
-
tokens: [/=(?<content>\w+)/, /[\s\/\>]/],
|
|
233
|
-
tokenOE: 'omit-eject',
|
|
234
|
-
};
|
|
235
|
-
const unquotedValueNode = new BasicNode(unquotedValueNodeOptions);
|
|
236
|
-
const innerNodeOptions = {
|
|
237
|
-
label: 'inner',
|
|
238
|
-
tokens: ['>', '</'],
|
|
239
|
-
tokenOE: '-eject',
|
|
240
|
-
};
|
|
241
|
-
const htmlBlockTypes = [
|
|
242
|
-
{
|
|
243
|
-
key: 'for',
|
|
244
|
-
exprRequired: true,
|
|
245
|
-
compatible: ['if'],
|
|
246
|
-
opening: true,
|
|
247
|
-
renderOpen: (v) => `for (${v || ''}) {\n`,
|
|
248
|
-
},
|
|
249
|
-
{
|
|
250
|
-
key: 'if',
|
|
251
|
-
exprRequired: true,
|
|
252
|
-
compatible: ['for'],
|
|
253
|
-
opening: true,
|
|
254
|
-
renderOpen: (v) => `if (${v || ''}) {\n`,
|
|
255
|
-
},
|
|
256
|
-
{
|
|
257
|
-
key: 'else-if',
|
|
258
|
-
closingKey: 'IF',
|
|
259
|
-
exprRequired: true,
|
|
260
|
-
opening: false,
|
|
261
|
-
overtakes: ['if', 'else-if'],
|
|
262
|
-
renderOpen: (v) => `} else if (${v || ''}) {\n`,
|
|
263
|
-
},
|
|
264
|
-
{
|
|
265
|
-
key: 'else',
|
|
266
|
-
closingKey: 'IF',
|
|
267
|
-
exprRequired: false,
|
|
268
|
-
opening: false,
|
|
269
|
-
overtakes: ['if', 'else-if'],
|
|
270
|
-
renderOpen: () => '} else {\n',
|
|
271
|
-
},
|
|
272
|
-
];
|
|
273
|
-
const htmlBlockTypesO = {};
|
|
274
|
-
htmlBlockTypes.forEach(b => htmlBlockTypesO[b.key] = b);
|
|
275
|
-
const getHtmlParser = (opts, recognizeTextDirective = false) => {
|
|
276
|
-
const rootNode = new BasicNode({ icon: 'Html Mode' });
|
|
277
|
-
const docTypeNode = new BasicNode(docTypeNodeOptions);
|
|
278
|
-
const cDataNode = new BasicNode(cDataNodeOptions);
|
|
279
|
-
const commentNode = new BasicNode(commentNodeOptions);
|
|
280
|
-
const innerNode = new BasicNode(innerNodeOptions);
|
|
281
|
-
const stringExpressionNode = stringExpressionNodeFactory(opts.exprDelimeters);
|
|
282
|
-
const attrNode = attributeNodeFactory();
|
|
283
|
-
const attrBlockOperationNode = attributeNodeFactory('↳', opts.blockOperation, 'block');
|
|
284
|
-
const attrExprNode = attributeNodeFactory('≈', opts.attrExpression, 'expr');
|
|
285
|
-
const attrNodes = [attrBlockOperationNode, attrExprNode, attrNode];
|
|
286
|
-
const textDirectiveNode = new BasicNode({
|
|
287
|
-
icon: 'T',
|
|
288
|
-
tokens: [
|
|
289
|
-
new RegExp(`^\\s*(?:<!--)${escapeRegex(opts.directive)}\\s*text-mode-on\\s*-->\\s*$`, 'm'),
|
|
290
|
-
new RegExp(`^\\s*(?:<!--)${escapeRegex(opts.directive)}\\s*text-mode-off\\s*-->\\s*$`, 'm'),
|
|
291
|
-
],
|
|
292
|
-
tokenOE: 'omit-omit',
|
|
293
|
-
})
|
|
294
|
-
.onMatch(({ parserContext }) => parserContext.jump())
|
|
295
|
-
.onPop(({ parserContext }) => parserContext.jump());
|
|
296
|
-
const tagNode = new BasicNode({
|
|
297
|
-
tokens: [
|
|
298
|
-
/<(?<tag>[\w:\-\.]+)/,
|
|
299
|
-
({ customData }) => {
|
|
300
|
-
if (customData.isVoid)
|
|
301
|
-
return /\/?>\s*/;
|
|
302
|
-
if (customData.isText)
|
|
303
|
-
return new RegExp(`\\s*<\\/(?<endTag>${escapeRegex(customData.tag)})\\s*>\\s*`);
|
|
304
|
-
return /(?:\/\>|\<\/(?<endTag>[\w:\-\.]+)\s*\>\s*)/;
|
|
305
|
-
},
|
|
306
|
-
],
|
|
307
|
-
})
|
|
308
|
-
.onMatch(({ context, customData }) => {
|
|
309
|
-
context.icon = customData.tag,
|
|
310
|
-
customData.isVoid = opts.voidTags.includes(customData.tag);
|
|
311
|
-
customData.isText = opts.textTags.includes(customData.tag);
|
|
312
|
-
if (customData.isVoid) {
|
|
313
|
-
context.clearRecognizes(innerNode);
|
|
314
|
-
}
|
|
315
|
-
if (customData.isText && !recognizeTextDirective) {
|
|
316
|
-
context.addAbsorbs(innerNode, 'join');
|
|
317
|
-
}
|
|
318
|
-
})
|
|
319
|
-
.onBeforeChildParse((child, { customData }) => {
|
|
320
|
-
if (customData.isText && child.node === innerNode) {
|
|
321
|
-
child.clearRecognizes();
|
|
322
|
-
if (recognizeTextDirective) {
|
|
323
|
-
child.addRecognizes(textDirectiveNode);
|
|
324
|
-
}
|
|
325
|
-
child.removeOnAppendContent();
|
|
326
|
-
child.endsWith = {
|
|
327
|
-
token: new RegExp(`<\\/(?<endTag>${escapeRegex(customData.tag)})\\s*>`),
|
|
328
|
-
eject: true,
|
|
329
|
-
};
|
|
330
|
-
}
|
|
331
|
-
})
|
|
332
|
-
.onAfterChildParse((child, { context }) => {
|
|
333
|
-
if (child.node === innerNode) {
|
|
334
|
-
context.clearRecognizes();
|
|
335
|
-
}
|
|
336
|
-
})
|
|
337
|
-
.onPop(({ customData: { isVoid, tag, endTag, operations }, parserContext, customData, context }) => {
|
|
338
|
-
var _a, _b;
|
|
339
|
-
if (operations && operations.length) {
|
|
340
|
-
operations = operations.sort((a) => a.key === 'for' ? -1 : 1);
|
|
341
|
-
customData.operation = operations[0].key;
|
|
342
|
-
for (let i = 0; i < operations.length; i++) {
|
|
343
|
-
const { key, value } = operations[i];
|
|
344
|
-
const descr = htmlBlockTypesO[key];
|
|
345
|
-
if (!descr) {
|
|
346
|
-
parserContext.panicBlock(`Unknown block operation "${key}".`, tag.length, tag.length + 1);
|
|
347
|
-
}
|
|
348
|
-
if (descr.exprRequired && !value) {
|
|
349
|
-
parserContext.panicBlock(`Expression required for "${key}" operation.`, tag.length, tag.length + 1);
|
|
350
|
-
}
|
|
351
|
-
if (!descr.exprRequired && !!value) {
|
|
352
|
-
parserContext.panicBlock(`Unexpected expression for "${key}" operation.`, tag.length, tag.length + 1);
|
|
353
|
-
}
|
|
354
|
-
if (descr.overtakes) {
|
|
355
|
-
const prevNode = context.getPrevNode();
|
|
356
|
-
const prevContext = context.getPrevContext();
|
|
357
|
-
if (typeof prevNode === 'string' && prevNode.replace(/[\s\n\r]/g, '')) {
|
|
358
|
-
parserContext.panicBlock(`Unexpected block operation "${descr.key}".`, tag.length, tag.length + 1);
|
|
359
|
-
}
|
|
360
|
-
if (!prevContext ||
|
|
361
|
-
!descr.overtakes.includes(prevContext.getCustomData().operation)) {
|
|
362
|
-
parserContext.panicBlock(`Unexpected block operation "${descr.key}".`, tag.length, tag.length + 1);
|
|
363
|
-
}
|
|
364
|
-
const prevData = prevContext.getCustomData();
|
|
365
|
-
prevData.closeCode = '';
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
if (operations.length === 1) {
|
|
369
|
-
const descr = htmlBlockTypesO[operations[0].key];
|
|
370
|
-
customData.openCode = descr.renderOpen(operations[0].value);
|
|
371
|
-
customData.closeCode = '}\n';
|
|
372
|
-
}
|
|
373
|
-
else if (operations.length === 2) {
|
|
374
|
-
const descr1 = htmlBlockTypesO[operations[0].key];
|
|
375
|
-
const descr2 = htmlBlockTypesO[operations[1].key];
|
|
376
|
-
if (!((_a = descr1.compatible) === null || _a === void 0 ? void 0 : _a.includes(descr2.key)) || !((_b = descr2.compatible) === null || _b === void 0 ? void 0 : _b.includes(descr1.key))) {
|
|
377
|
-
parserContext.panicBlock(`Block operation "${descr1.key}" is not compatible with "${descr2.key}".`, tag.length, tag.length + 1);
|
|
378
|
-
}
|
|
379
|
-
customData.openCode = descr1.renderOpen(operations[0].value) + descr2.renderOpen(operations[1].value);
|
|
380
|
-
customData.closeCode = '}\n}\n';
|
|
381
|
-
}
|
|
382
|
-
else {
|
|
383
|
-
parserContext.panicBlock(`Too many block operations "${operations.map(o => o.key).join(', ')}".`, tag.length, tag.length + 1);
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
if (!isVoid && typeof endTag === 'string' && tag !== endTag) {
|
|
387
|
-
parserContext.panicBlock(`Open tag <${tag}> and closing tag </${endTag}> must be equal.`, tag.length || 0, endTag.length + 1);
|
|
388
|
-
}
|
|
389
|
-
})
|
|
390
|
-
.addRecognizes(innerNode, ...attrNodes)
|
|
391
|
-
.addHoistChildren({
|
|
392
|
-
node: attrBlockOperationNode,
|
|
393
|
-
as: 'operations',
|
|
394
|
-
asArray: true,
|
|
395
|
-
deep: 1,
|
|
396
|
-
mapRule: 'customData',
|
|
397
|
-
});
|
|
398
|
-
if (recognizeTextDirective) {
|
|
399
|
-
rootNode.addRecognizes(textDirectiveNode);
|
|
400
|
-
innerNode.addRecognizes(textDirectiveNode);
|
|
401
|
-
cDataNode.addRecognizes(textDirectiveNode);
|
|
402
|
-
}
|
|
403
|
-
rootNode.addRecognizes(docTypeNode, commentNode, tagNode, stringExpressionNode);
|
|
404
|
-
innerNode.addRecognizes(commentNode, cDataNode, tagNode, stringExpressionNode);
|
|
405
|
-
commentNode.addRecognizes(stringExpressionNode);
|
|
406
|
-
cDataNode.addRecognizes(stringExpressionNode);
|
|
407
|
-
return {
|
|
408
|
-
rootNode,
|
|
409
|
-
innerNode,
|
|
410
|
-
commentNode,
|
|
411
|
-
cDataNode,
|
|
412
|
-
docTypeNode,
|
|
413
|
-
stringExpressionNode,
|
|
414
|
-
attrNode,
|
|
415
|
-
attrBlockOperationNode,
|
|
416
|
-
attrExprNode,
|
|
417
|
-
tagNode,
|
|
418
|
-
textDirectiveNode,
|
|
419
|
-
};
|
|
420
|
-
};
|
|
421
|
-
const getHtmlRewriter = (opts, debug = false) => {
|
|
422
|
-
return getRewriter(getHtmlParser(opts).rootNode, debug);
|
|
423
|
-
};
|
|
424
|
-
function attributeNodeFactory(icon = '=', prefix, type = 'plain') {
|
|
425
|
-
const attrNodeOptions = {
|
|
426
|
-
label: '',
|
|
427
|
-
icon,
|
|
428
|
-
tokens: [
|
|
429
|
-
prefix ? new RegExp(`${escapeRegex(prefix)}(?<key>[\\w:\\-\\.]+)`) : /(?<key>[\w:\-\.]+)/,
|
|
430
|
-
/[\s\n\/>]/,
|
|
431
|
-
],
|
|
432
|
-
tokenOE: 'omit-eject',
|
|
433
|
-
};
|
|
434
|
-
const quoteEsc = {
|
|
435
|
-
'"': '"',
|
|
436
|
-
'\'': ''',
|
|
437
|
-
};
|
|
438
|
-
function escapeVal(value, quote) {
|
|
439
|
-
let v = '';
|
|
440
|
-
if (value) {
|
|
441
|
-
v += '=';
|
|
442
|
-
v += quote ? (quote + value.replace(new RegExp(quote, 'g'), quoteEsc[quote]) + quote) : value;
|
|
443
|
-
}
|
|
444
|
-
return v;
|
|
445
|
-
}
|
|
446
|
-
const children = [unquotedValueNode, valueNode];
|
|
447
|
-
const codeFuncs = {
|
|
448
|
-
plain: ({ customData: { key, value, quote } }, level = 0) => {
|
|
449
|
-
return ' '.repeat(level * 2) + pushString(key + escapeVal(value, quote));
|
|
450
|
-
},
|
|
451
|
-
block: () => '',
|
|
452
|
-
expr: ({ customData: { key, value, quote } }, level = 0) => {
|
|
453
|
-
const q = (quote || '');
|
|
454
|
-
const indent = ' '.repeat(level * 2);
|
|
455
|
-
let s = indent + '__v = ' + (value || '\'\'') + '\n';
|
|
456
|
-
s += indent + 'if (typeof __v === \'boolean\') {\n';
|
|
457
|
-
s += indent + ` if (__v) __ += \`${key}\`\n`;
|
|
458
|
-
s += indent + '} else {\n';
|
|
459
|
-
s += indent + ` __ += \`${key}=${q}` + '${' + ` (__v || '').replace(/${q}/g, '${quoteEsc[q]}') }${q}\`\n`;
|
|
460
|
-
s += indent + '}\n';
|
|
461
|
-
return s;
|
|
462
|
-
},
|
|
463
|
-
};
|
|
464
|
-
const attrNode = new BasicNode(attrNodeOptions)
|
|
465
|
-
.addPopsAfterNode(...children)
|
|
466
|
-
.addHoistChildren({
|
|
467
|
-
node: valueNode,
|
|
468
|
-
as: 'quote',
|
|
469
|
-
deep: 1,
|
|
470
|
-
mapRule: 'customData.quote',
|
|
471
|
-
})
|
|
472
|
-
.onPop(({ parserContext, customData: { value, quote } }) => {
|
|
473
|
-
if (type === 'expr' && value) {
|
|
474
|
-
try {
|
|
475
|
-
new Function(value);
|
|
476
|
-
}
|
|
477
|
-
catch (e) {
|
|
478
|
-
parserContext.panic('Invalid expression: ' + e.message, value.length + 1);
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
if (type === 'expr' && !quote) {
|
|
482
|
-
parserContext.panic('Expression must be quoted.', (value === null || value === void 0 ? void 0 : value.length) || 1);
|
|
483
|
-
}
|
|
484
|
-
})
|
|
485
|
-
.addAbsorbs(children, 'join->value')
|
|
486
|
-
.initCustomData(() => ({
|
|
487
|
-
key: '',
|
|
488
|
-
code: codeFuncs[type],
|
|
489
|
-
}));
|
|
490
|
-
return attrNode;
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
const textBlockTypes = [
|
|
494
|
-
{
|
|
495
|
-
key: 'FOR',
|
|
496
|
-
exprRequired: true,
|
|
497
|
-
opening: true,
|
|
498
|
-
code: ({ customData }) => `for ${customData.expression} {\n`,
|
|
499
|
-
},
|
|
500
|
-
{
|
|
501
|
-
key: 'IF',
|
|
502
|
-
exprRequired: true,
|
|
503
|
-
opening: true,
|
|
504
|
-
code: ({ customData }) => `if ${customData.expression} {\n`,
|
|
505
|
-
},
|
|
506
|
-
{
|
|
507
|
-
key: 'ELSEIF',
|
|
508
|
-
closingKey: 'IF',
|
|
509
|
-
exprRequired: true,
|
|
510
|
-
opening: false,
|
|
511
|
-
overtakes: ['IF', 'ELSEIF'],
|
|
512
|
-
code: ({ customData }) => `} else if ${customData.expression} {\n`,
|
|
513
|
-
},
|
|
514
|
-
{
|
|
515
|
-
key: 'ELSE',
|
|
516
|
-
closingKey: 'IF',
|
|
517
|
-
exprRequired: false,
|
|
518
|
-
opening: false,
|
|
519
|
-
overtakes: ['IF', 'ELSEIF'],
|
|
520
|
-
code: () => '} else {\n',
|
|
521
|
-
},
|
|
522
|
-
];
|
|
523
|
-
const textBlockTypesO = {};
|
|
524
|
-
textBlockTypes.forEach(b => textBlockTypesO[b.key] = b);
|
|
525
|
-
const getTextParser = (opts, recognizeHtmlDirective = false) => {
|
|
526
|
-
const rootNode = new BasicNode({ icon: 'Text Mode' });
|
|
527
|
-
const stringExpressionNode = stringExpressionNodeFactory(opts.exprDelimeters);
|
|
528
|
-
const blockOperationNode = new BasicNode({
|
|
529
|
-
label: '',
|
|
530
|
-
icon: '↳',
|
|
531
|
-
tokens: [
|
|
532
|
-
new RegExp(`^\\s*(?:\\/\\/|#)${escapeRegex(opts.blockOperation)}\\s*(?<block>\\w+[^\\S\\r\\n]*\\w*)[^\\S\\r\\n]*(?<expression>\\(.*\\))?(?<rest>[^\\n]*)?$`, 'm'),
|
|
533
|
-
new RegExp(`^\\s*(?:\\/\\/|#)${escapeRegex(opts.blockOperation)}\\s*(?<endBlock>END[^\\S\\r\\n]*\\w*)[^\\S\\r\\n]*(?<endExpression>\\(.*\\))?(?<endRest>[^\\n]*)?$`, 'm'),
|
|
534
|
-
],
|
|
535
|
-
tokenOE: 'omit-omit',
|
|
536
|
-
})
|
|
537
|
-
.onMatchStartToken(({ customData, parserContext, matched }) => {
|
|
538
|
-
const { block, expression, rest } = matched.groups;
|
|
539
|
-
const descr = textBlockTypesO[block.replace(/[\s\n]/g, '')];
|
|
540
|
-
if (!descr) {
|
|
541
|
-
parserContext.panic(`Unknown block operation "${block.trim()}".`, -matched[0].indexOf(block));
|
|
542
|
-
}
|
|
543
|
-
if (descr.exprRequired && !expression) {
|
|
544
|
-
parserContext.panic(`Expression required for "${block.trim()}" operation.`, -matched[0].length);
|
|
545
|
-
}
|
|
546
|
-
if (!descr.exprRequired && !!expression) {
|
|
547
|
-
parserContext.panic(`Unexpected expression for "${block.trim()}" operation.`, -matched[0].indexOf(expression));
|
|
548
|
-
}
|
|
549
|
-
if (rest) {
|
|
550
|
-
parserContext.panic(`Unexpected text "${rest}" in "${block.trim()}" operation.`, -matched[0].length + rest.length);
|
|
551
|
-
}
|
|
552
|
-
if (descr.opening) {
|
|
553
|
-
return true;
|
|
554
|
-
}
|
|
555
|
-
if (descr.overtakes && descr.overtakes.includes(customData.block)) {
|
|
556
|
-
parserContext.pop();
|
|
557
|
-
return true;
|
|
558
|
-
}
|
|
559
|
-
if (customData.block) {
|
|
560
|
-
parserContext.panic(`Unexpected block operation "${block.trim()}" after "${customData.block}" block.`);
|
|
561
|
-
}
|
|
562
|
-
else {
|
|
563
|
-
parserContext.panic(`Unexpected block operation "${block.trim()}".`);
|
|
564
|
-
}
|
|
565
|
-
})
|
|
566
|
-
.onMatch(({ customData, context, parserContext }) => {
|
|
567
|
-
customData.block = customData.block.replace(/[\s\n]/g, '');
|
|
568
|
-
customData.descr = textBlockTypesO[customData.block];
|
|
569
|
-
customData.openCode = customData.descr.code(context);
|
|
570
|
-
parserContext.jump();
|
|
571
|
-
})
|
|
572
|
-
.onPop(({ parserContext }) => parserContext.jump())
|
|
573
|
-
.onMatchEndToken(({ customData, matched, parserContext }) => {
|
|
574
|
-
const { endBlock } = matched.groups;
|
|
575
|
-
const endKey = 'END' + (customData.descr.closingKey || customData.descr.key);
|
|
576
|
-
if (endKey !== endBlock.replace(/[\s\n]/g, '')) {
|
|
577
|
-
parserContext.panic('Wrong closing block instruction ' + endBlock, matched[0].length);
|
|
578
|
-
}
|
|
579
|
-
customData.closeCode = '}\n';
|
|
580
|
-
return true;
|
|
581
|
-
})
|
|
582
|
-
.popsAtEOFSource(true);
|
|
583
|
-
const revealNode = new BasicNode({
|
|
584
|
-
icon: ':',
|
|
585
|
-
tokens: [
|
|
586
|
-
new RegExp(`^\\s*(?:\\/\\/|#)${escapeRegex(opts.revealLine)}[^\\S\\r\\n]*`, 'm'),
|
|
587
|
-
/$/m,
|
|
588
|
-
],
|
|
589
|
-
tokenOE: 'omit-',
|
|
590
|
-
})
|
|
591
|
-
.addRecognizes(stringExpressionNode)
|
|
592
|
-
.popsAtEOFSource(true);
|
|
593
|
-
const ignoreDirectiveNode = new BasicNode({
|
|
594
|
-
icon: ':',
|
|
595
|
-
tokens: [
|
|
596
|
-
new RegExp(`^\\s*(?:\\/\\/|#)${escapeRegex(opts.directive)}\\s*ignore-next-line\\s*$`, 'm'),
|
|
597
|
-
/^[^\n]*$/m,
|
|
598
|
-
],
|
|
599
|
-
tokenOE: 'omit-',
|
|
600
|
-
})
|
|
601
|
-
.popsAtEOFSource(true);
|
|
602
|
-
const htmlDirectiveNode = new BasicNode({
|
|
603
|
-
icon: 'H',
|
|
604
|
-
tokens: [
|
|
605
|
-
new RegExp(`^\\s*(?:\\/\\/|#)${escapeRegex(opts.directive)}\\s*html-mode-on\\s*$`, 'm'),
|
|
606
|
-
new RegExp(`^\\s*(?:\\/\\/|#)${escapeRegex(opts.directive)}\\s*html-mode-off\\s*$`, 'm'),
|
|
607
|
-
],
|
|
608
|
-
tokenOE: 'omit-omit',
|
|
609
|
-
})
|
|
610
|
-
.onMatch(({ parserContext }) => parserContext.jump())
|
|
611
|
-
.onPop(({ parserContext }) => parserContext.jump());
|
|
612
|
-
blockOperationNode.addRecognizes(ignoreDirectiveNode, blockOperationNode, revealNode, stringExpressionNode);
|
|
613
|
-
rootNode.addRecognizes(ignoreDirectiveNode, blockOperationNode, revealNode, stringExpressionNode);
|
|
614
|
-
if (recognizeHtmlDirective) {
|
|
615
|
-
rootNode.addRecognizes(htmlDirectiveNode);
|
|
616
|
-
blockOperationNode.addRecognizes(htmlDirectiveNode);
|
|
617
|
-
}
|
|
618
|
-
return {
|
|
619
|
-
stringExpressionNode,
|
|
620
|
-
revealNode,
|
|
621
|
-
ignoreDirectiveNode,
|
|
622
|
-
blockOperationNode,
|
|
623
|
-
rootNode,
|
|
624
|
-
htmlDirectiveNode,
|
|
625
|
-
};
|
|
626
|
-
};
|
|
627
|
-
const getTextRewriter = (opts, debug = false) => {
|
|
628
|
-
return getRewriter(getTextParser(opts).rootNode, debug);
|
|
629
|
-
};
|
|
630
|
-
|
|
631
|
-
const defaultDelimeters = ['{{', '}}'];
|
|
632
|
-
class ProstoRewrite {
|
|
633
|
-
constructor(options) {
|
|
634
|
-
var _a, _b;
|
|
635
|
-
this.options = {
|
|
636
|
-
defaultMode: (options === null || options === void 0 ? void 0 : options.defaultMode) || 'auto',
|
|
637
|
-
debug: (options === null || options === void 0 ? void 0 : options.debug) || false,
|
|
638
|
-
htmlPattern: [],
|
|
639
|
-
textPattern: [],
|
|
640
|
-
html: {
|
|
641
|
-
exprDelimeters: defaultDelimeters,
|
|
642
|
-
attrExpression: ':',
|
|
643
|
-
blockOperation: 'v-',
|
|
644
|
-
directive: '!@',
|
|
645
|
-
voidTags: ((_a = options === null || options === void 0 ? void 0 : options.html) === null || _a === void 0 ? void 0 : _a.voidTags) || htmlVoidTags,
|
|
646
|
-
textTags: ((_b = options === null || options === void 0 ? void 0 : options.html) === null || _b === void 0 ? void 0 : _b.textTags) || htmlTextTags,
|
|
647
|
-
...options === null || options === void 0 ? void 0 : options.html,
|
|
648
|
-
},
|
|
649
|
-
text: {
|
|
650
|
-
exprDelimeters: defaultDelimeters,
|
|
651
|
-
blockOperation: '=',
|
|
652
|
-
revealLine: ':',
|
|
653
|
-
directive: '!@',
|
|
654
|
-
...options === null || options === void 0 ? void 0 : options.text,
|
|
655
|
-
},
|
|
656
|
-
};
|
|
657
|
-
}
|
|
658
|
-
get mixedRewriter() {
|
|
659
|
-
const textNodes = getTextParser(this.options.text, true);
|
|
660
|
-
const htmlNodes = getHtmlParser(this.options.html, true);
|
|
661
|
-
textNodes.htmlDirectiveNode.addRecognizes(...htmlNodes.rootNode.recognizes);
|
|
662
|
-
htmlNodes.textDirectiveNode.addRecognizes(...textNodes.rootNode.recognizes);
|
|
663
|
-
return {
|
|
664
|
-
text: getRewriter(textNodes.rootNode, this.options.debug),
|
|
665
|
-
html: getRewriter(htmlNodes.rootNode, this.options.debug),
|
|
666
|
-
};
|
|
667
|
-
}
|
|
668
|
-
get textRewriter() {
|
|
669
|
-
return getTextRewriter(this.options.text, this.options.debug);
|
|
670
|
-
}
|
|
671
|
-
get htmlRewriter() {
|
|
672
|
-
return getHtmlRewriter(this.options.html, this.options.debug);
|
|
673
|
-
}
|
|
674
|
-
async rewriteFile(opts, context) {
|
|
675
|
-
const file = await readFile(opts.input);
|
|
676
|
-
const mode = this.determineMode(opts.mode || this.options.defaultMode, opts.input, file);
|
|
677
|
-
let output = file;
|
|
678
|
-
const modes = this.mixedRewriter;
|
|
679
|
-
if (mode) {
|
|
680
|
-
this.debug('Parsing file in ' + mode + ' mode. ' + '[4m' + '[22m' + opts.input);
|
|
681
|
-
output = modes[mode].rewrite(file, context);
|
|
682
|
-
}
|
|
683
|
-
else {
|
|
684
|
-
this.debug('File isn\'t recognized as text nor as html. ' + '[4m' + '[22m' + opts.input);
|
|
685
|
-
}
|
|
686
|
-
if (opts.output) {
|
|
687
|
-
if (mode) {
|
|
688
|
-
await writeFile(opts.output);
|
|
689
|
-
this.debug('File has been rewritten in ' + mode + ' mode. ' + '[4m' + '[22m' + opts.output);
|
|
690
|
-
}
|
|
691
|
-
else {
|
|
692
|
-
await copyFile(opts.input, opts.output);
|
|
693
|
-
this.debug('File rewriting was skipped. File copied. ' + '[4m' + '[22m' + opts.output);
|
|
694
|
-
}
|
|
695
|
-
}
|
|
696
|
-
return output;
|
|
697
|
-
}
|
|
698
|
-
async rewriteDir(opts, context) {
|
|
699
|
-
return;
|
|
700
|
-
}
|
|
701
|
-
determineMode(mode, path, content) {
|
|
702
|
-
if (mode === 'text')
|
|
703
|
-
return 'text';
|
|
704
|
-
if (mode === 'html')
|
|
705
|
-
return 'html';
|
|
706
|
-
if (mode === 'auto') {
|
|
707
|
-
const isText = !!this.options.textPattern.find(m => m.match(path));
|
|
708
|
-
const isHtml = !!this.options.htmlPattern.find(m => m.match(path));
|
|
709
|
-
if (isText && isHtml) {
|
|
710
|
-
if (/^<!DOCTYPE\s/.test(content))
|
|
711
|
-
return 'html';
|
|
712
|
-
if (/^<\?xml\s/.test(content))
|
|
713
|
-
return 'html';
|
|
714
|
-
return 'text';
|
|
715
|
-
}
|
|
716
|
-
if (isText)
|
|
717
|
-
return 'text';
|
|
718
|
-
if (isHtml)
|
|
719
|
-
return 'html';
|
|
720
|
-
}
|
|
721
|
-
}
|
|
722
|
-
debug(message) {
|
|
723
|
-
if (!this.options.debug)
|
|
724
|
-
return;
|
|
725
|
-
debug(message);
|
|
726
|
-
}
|
|
727
|
-
}
|
|
728
|
-
|
|
729
|
-
export { ProstoRewrite, genSafeFunc, getRewriter, getStringExpressionRewriter, pushString, renderCode };
|