deno.land / x / eta@v1.12.3 / parse.ts

View Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
import { ParseErr } from "./err.ts";import { trimWS } from "./utils.ts";
/* TYPES */
import type { EtaConfig } from "./config.ts";
export type TagType = "r" | "e" | "i" | "";
export interface TemplateObject { t: TagType; val: string;}
export type AstObject = string | TemplateObject;
/* END TYPES */
const templateLitReg = /`(?:\\[\s\S]|\${(?:[^{}]|{(?:[^{}]|{[^}]*})*})*}|(?!\${)[^\\`])*`/g;
const singleQuoteReg = /'(?:\\[\s\w"'\\`]|[^\n\r'\\])*?'/g;
const doubleQuoteReg = /"(?:\\[\s\w"'\\`]|[^\n\r"\\])*?"/g;
/** Escape special regular expression characters inside a string */
function escapeRegExp(string: string) { // From MDN return string.replace(/[.*+\-?^${}()|[\]\\]/g, "\\$&") // $& means the whole matched string ;}
export default function parse( str: string, config: EtaConfig,): Array<AstObject> { let buffer: Array<AstObject> = []; let trimLeftOfNextStr: string | false = false; let lastIndex = 0; const parseOptions = config.parse;
if (config.plugins) { for (let i = 0; i < config.plugins.length; i++) { const plugin = config.plugins[i]; if (plugin.processTemplate) { str = plugin.processTemplate(str, config); } } }
/* Adding for EJS compatibility */ if (config.rmWhitespace) { // Code taken directly from EJS // Have to use two separate replaces here as `^` and `$` operators don't // work well with `\r` and empty lines don't work well with the `m` flag. // Essentially, this replaces the whitespace at the beginning and end of // each line and removes multiple newlines. str = str.replace(/[\r\n]+/g, "\n").replace(/^\s+|\s+$/gm, ""); } /* End rmWhitespace option */
templateLitReg.lastIndex = 0; singleQuoteReg.lastIndex = 0; doubleQuoteReg.lastIndex = 0;
function pushString(strng: string, shouldTrimRightOfString?: string | false) { if (strng) { // if string is truthy it must be of type 'string'
strng = trimWS( strng, config, trimLeftOfNextStr, // this will only be false on the first str, the next ones will be null or undefined shouldTrimRightOfString, );
if (strng) { // replace \ with \\, ' with \' // we're going to convert all CRLF to LF so it doesn't take more than one replace
strng = strng.replace(/\\|'/g, "\\$&").replace(/\r\n|\n|\r/g, "\\n");
buffer.push(strng); } } }
const prefixes = [ parseOptions.exec, parseOptions.interpolate, parseOptions.raw, ].reduce(function ( accumulator, prefix, ) { if (accumulator && prefix) { return accumulator + "|" + escapeRegExp(prefix); } else if (prefix) { // accumulator is falsy return escapeRegExp(prefix); } else { // prefix and accumulator are both falsy return accumulator; } }, "");
const parseOpenReg = new RegExp( "([^]*?)" + escapeRegExp(config.tags[0]) + "(-|_)?\\s*(" + prefixes + ")?\\s*", "g", );
const parseCloseReg = new RegExp( "'|\"|`|\\/\\*|(\\s*(-|_)?" + escapeRegExp(config.tags[1]) + ")", "g", ); // TODO: benchmark having the \s* on either side vs using str.trim()
let m;
while ((m = parseOpenReg.exec(str))) { lastIndex = m[0].length + m.index;
const precedingString = m[1]; const wsLeft = m[2]; const prefix = m[3] || ""; // by default either ~, =, or empty
pushString(precedingString, wsLeft);
parseCloseReg.lastIndex = lastIndex; let closeTag; let currentObj: AstObject | false = false;
while ((closeTag = parseCloseReg.exec(str))) { if (closeTag[1]) { let content = str.slice(lastIndex, closeTag.index);
parseOpenReg.lastIndex = lastIndex = parseCloseReg.lastIndex;
trimLeftOfNextStr = closeTag[2];
const currentType: TagType = prefix === parseOptions.exec ? "e" : prefix === parseOptions.raw ? "r" : prefix === parseOptions.interpolate ? "i" : "";
currentObj = { t: currentType, val: content }; break; } else { const char = closeTag[0]; if (char === "/*") { const commentCloseInd = str.indexOf("*/", parseCloseReg.lastIndex);
if (commentCloseInd === -1) { ParseErr("unclosed comment", str, closeTag.index); } parseCloseReg.lastIndex = commentCloseInd; } else if (char === "'") { singleQuoteReg.lastIndex = closeTag.index;
const singleQuoteMatch = singleQuoteReg.exec(str); if (singleQuoteMatch) { parseCloseReg.lastIndex = singleQuoteReg.lastIndex; } else { ParseErr("unclosed string", str, closeTag.index); } } else if (char === '"') { doubleQuoteReg.lastIndex = closeTag.index; const doubleQuoteMatch = doubleQuoteReg.exec(str);
if (doubleQuoteMatch) { parseCloseReg.lastIndex = doubleQuoteReg.lastIndex; } else { ParseErr("unclosed string", str, closeTag.index); } } else if (char === "`") { templateLitReg.lastIndex = closeTag.index; const templateLitMatch = templateLitReg.exec(str); if (templateLitMatch) { parseCloseReg.lastIndex = templateLitReg.lastIndex; } else { ParseErr("unclosed string", str, closeTag.index); } } } } if (currentObj) { buffer.push(currentObj); } else { ParseErr("unclosed tag", str, m.index + precedingString.length); } }
pushString(str.slice(lastIndex, str.length), false);
if (config.plugins) { for (let i = 0; i < config.plugins.length; i++) { const plugin = config.plugins[i]; if (plugin.processAST) { buffer = plugin.processAST(buffer, config); } } }
return buffer;}
eta
Embedded JS template engine for Node, Deno, and the browser. Lighweight, fast, and pluggable. Written in TypeScript
DocumentationDocumentation
GitHub Repositoryeta-dev/eta
GitHub Stars
527

Version Info

Tagged at
10 months ago

External Dependencies

1 external dependency