ventojs 0.12.1 → 0.12.2

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.
@@ -1,54 +0,0 @@
1
- import { Token } from "./tokenizer.js";
2
- import type { Loader } from "./loader.js";
3
- export interface TemplateResult {
4
- content: string;
5
- [key: string]: unknown;
6
- }
7
- export interface Template {
8
- (data?: Record<string, unknown>): Promise<TemplateResult>;
9
- source: string;
10
- code: string;
11
- file?: string;
12
- }
13
- export interface TemplateSync {
14
- (data?: Record<string, unknown>): TemplateResult;
15
- source: string;
16
- code: string;
17
- file?: string;
18
- }
19
- export type TokenPreprocessor = (env: Environment, tokens: Token[], path?: string) => Token[] | void;
20
- export type Tag = (env: Environment, code: string, output: string, tokens: Token[]) => string | undefined;
21
- export type FilterThis = {
22
- data: Record<string, unknown>;
23
- env: Environment;
24
- };
25
- export type Filter = (this: FilterThis, ...args: any[]) => any;
26
- export type Plugin = (env: Environment) => void;
27
- export interface Options {
28
- loader: Loader;
29
- dataVarname: string;
30
- autoescape: boolean;
31
- useWith: boolean;
32
- }
33
- export declare class Environment {
34
- cache: Map<string, Template>;
35
- options: Options;
36
- tags: Tag[];
37
- tokenPreprocessors: TokenPreprocessor[];
38
- filters: Record<string, Filter>;
39
- utils: Record<string, unknown>;
40
- constructor(options: Options);
41
- use(plugin: Plugin): void;
42
- run(file: string, data: Record<string, unknown>, from?: string): Promise<TemplateResult>;
43
- runString(source: string, data?: Record<string, unknown>, file?: string): Promise<TemplateResult>;
44
- runStringSync(source: string, data?: Record<string, unknown>): TemplateResult;
45
- compile(source: string, path?: string, defaults?: Record<string, unknown>, sync?: false): Template;
46
- compile(source: string, path?: string, defaults?: Record<string, unknown>, sync?: true): TemplateSync;
47
- tokenize(source: string, path?: string): Token[];
48
- load(file: string, from?: string): Promise<Template>;
49
- compileTokens(tokens: Token[], outputVar?: string, stopAt?: string[]): string[];
50
- compileFilters(tokens: Token[], output: string, autoescape?: boolean): string;
51
- createError(path: string, source: string, position: number, cause: Error): Error;
52
- }
53
- /** Returns the number and code of the errored line */
54
- export declare function errorLine(source: string, pos: number): [number, number, string];
@@ -1,198 +0,0 @@
1
- import * as dntShim from "../_dnt.shims.js";
2
- import tokenize from "./tokenizer.js";
3
- import { transformTemplateCode } from "./transformer.js";
4
- export class Environment {
5
- cache = new Map();
6
- options;
7
- tags = [];
8
- tokenPreprocessors = [];
9
- filters = {};
10
- utils = {};
11
- constructor(options) {
12
- this.options = options;
13
- }
14
- use(plugin) {
15
- plugin(this);
16
- }
17
- async run(file, data, from) {
18
- const template = await this.load(file, from);
19
- return await template(data);
20
- }
21
- async runString(source, data, file) {
22
- if (file) {
23
- const cached = this.cache.get(file);
24
- if (cached) {
25
- return await cached(data);
26
- }
27
- const template = this.compile(source, file);
28
- this.cache.set(file, template);
29
- return await template(data);
30
- }
31
- const template = this.compile(source, file);
32
- return await template(data);
33
- }
34
- runStringSync(source, data) {
35
- const template = this.compile(source, "", {}, true);
36
- return template(data);
37
- }
38
- compile(source, path, defaults, sync = false) {
39
- const tokens = this.tokenize(source, path);
40
- let code = this.compileTokens(tokens).join("\n");
41
- const { dataVarname, useWith } = this.options;
42
- if (useWith) {
43
- code = transformTemplateCode(code, dataVarname);
44
- }
45
- const constructor = new Function("__file", "__env", "__defaults", `return${sync ? "" : " async"} function (${dataVarname}) {
46
- let __pos = 0;
47
- try {
48
- ${dataVarname} = Object.assign({}, __defaults, ${dataVarname});
49
- const __exports = { content: "" };
50
- ${code}
51
- return __exports;
52
- } catch (cause) {
53
- const template = __env.cache.get(__file);
54
- throw __env.createError(__file, template?.source || "", __pos, cause);
55
- }
56
- }
57
- `);
58
- const template = constructor(path, this, defaults);
59
- template.file = path;
60
- template.code = code;
61
- template.source = source;
62
- return template;
63
- }
64
- tokenize(source, path) {
65
- const result = tokenize(source);
66
- let { tokens } = result;
67
- const { position, error } = result;
68
- if (error) {
69
- throw this.createError(path || "unknown", source, position, error);
70
- }
71
- for (const tokenPreprocessor of this.tokenPreprocessors) {
72
- const result = tokenPreprocessor(this, tokens, path);
73
- if (result !== undefined) {
74
- tokens = result;
75
- }
76
- }
77
- return tokens;
78
- }
79
- async load(file, from) {
80
- const path = from ? this.options.loader.resolve(from, file) : file;
81
- if (!this.cache.has(path)) {
82
- // Remove query and hash params from path before loading
83
- const cleanPath = path
84
- .split("?")[0]
85
- .split("#")[0];
86
- const { source, data } = await this.options.loader.load(cleanPath);
87
- const template = this.compile(source, path, data);
88
- this.cache.set(path, template);
89
- }
90
- return this.cache.get(path);
91
- }
92
- compileTokens(tokens, outputVar = "__exports.content", stopAt) {
93
- const compiled = [];
94
- tokens: while (tokens.length > 0) {
95
- if (stopAt && tokens[0][0] === "tag" && stopAt.includes(tokens[0][1])) {
96
- break;
97
- }
98
- const [type, code, pos] = tokens.shift();
99
- if (type === "comment") {
100
- continue;
101
- }
102
- if (type === "string") {
103
- compiled.push(`${outputVar} += ${JSON.stringify(code)};`);
104
- continue;
105
- }
106
- if (type === "tag") {
107
- compiled.push(`__pos = ${pos};`);
108
- for (const tag of this.tags) {
109
- const compiledTag = tag(this, code, outputVar, tokens);
110
- if (typeof compiledTag === "string") {
111
- compiled.push(compiledTag);
112
- continue tokens;
113
- }
114
- }
115
- // Unknown tag, just print it
116
- const expression = this.compileFilters(tokens, code, this.options.autoescape);
117
- compiled.push(`${outputVar} += (${expression}) ?? "";`);
118
- continue;
119
- }
120
- throw new Error(`Unknown token type "${type}"`);
121
- }
122
- return compiled;
123
- }
124
- compileFilters(tokens, output, autoescape = false) {
125
- let unescaped = false;
126
- while (tokens.length > 0 && tokens[0][0] === "filter") {
127
- const [, code] = tokens.shift();
128
- const match = code.match(/^(await\s+)?([\w.]+)(?:\((.*)\))?$/);
129
- if (!match) {
130
- throw new Error(`Invalid filter: ${code}`);
131
- }
132
- const [_, isAsync, name, args] = match;
133
- if (!this.filters[name]) {
134
- if (name === "safe") {
135
- unescaped = true;
136
- }
137
- else if (isGlobal(name)) {
138
- // If a global function
139
- output = `${isAsync ? "await " : ""}${name}(${output}${args ? `, ${args}` : ""})`;
140
- }
141
- else {
142
- // It's a prototype's method (e.g. `String.toUpperCase()`)
143
- output = `${isAsync ? "await " : ""}(${output})?.${name}?.(${args ? args : ""})`;
144
- }
145
- }
146
- else {
147
- // It's a filter (e.g. filters.upper())
148
- const { dataVarname } = this.options;
149
- output = `${(isAsync || checkAsync(this.filters[name])) ? "await " : ""}__env.filters.${name}.call({data:${dataVarname},env:__env}, ${output}${args ? `, ${args}` : ""})`;
150
- }
151
- }
152
- // Escape by default
153
- if (autoescape && !unescaped) {
154
- output = `__env.filters.escape(${output})`;
155
- }
156
- return output;
157
- }
158
- createError(path, source, position, cause) {
159
- if (!source) {
160
- return cause;
161
- }
162
- const [line, column, code] = errorLine(source, position);
163
- return new Error(`Error in the template ${path}:${line}:${column}\n\n${code.trim()}\n\n> ${cause.message}\n`, { cause });
164
- }
165
- }
166
- function isGlobal(name) {
167
- // @ts-ignore TS doesn't know about globalThis
168
- if (dntShim.dntGlobalThis[name]) {
169
- return true;
170
- }
171
- if (name.includes(".")) {
172
- const [obj, prop] = name.split(".");
173
- // @ts-ignore TS doesn't know about globalThis
174
- return typeof dntShim.dntGlobalThis[obj]?.[prop] === "function";
175
- }
176
- }
177
- /** Returns the number and code of the errored line */
178
- export function errorLine(source, pos) {
179
- let line = 1;
180
- let column = 1;
181
- for (let index = 0; index < pos; index++) {
182
- if (source[index] === "\n" ||
183
- (source[index] === "\r" && source[index + 1] === "\n")) {
184
- line++;
185
- column = 1;
186
- if (source[index] === "\r") {
187
- index++;
188
- }
189
- }
190
- else {
191
- column++;
192
- }
193
- }
194
- return [line, column, source.split("\n")[line - 1]];
195
- }
196
- function checkAsync(fn) {
197
- return fn.constructor?.name === "AsyncFunction";
198
- }
@@ -1,14 +0,0 @@
1
- export interface TemplateSource {
2
- source: string;
3
- data?: Record<string, unknown>;
4
- }
5
- export interface Loader {
6
- load(file: string): TemplateSource | Promise<TemplateSource>;
7
- resolve(from: string, file: string): string;
8
- }
9
- export declare class FileLoader implements Loader {
10
- #private;
11
- constructor(root: string);
12
- load(file: string): Promise<TemplateSource>;
13
- resolve(from: string, file: string): string;
14
- }
package/esm/src/loader.js DELETED
@@ -1,19 +0,0 @@
1
- import * as dntShim from "../_dnt.shims.js";
2
- import { path } from "../deps.js";
3
- export class FileLoader {
4
- #root;
5
- constructor(root) {
6
- this.#root = root;
7
- }
8
- async load(file) {
9
- return {
10
- source: await dntShim.Deno.readTextFile(file),
11
- };
12
- }
13
- resolve(from, file) {
14
- if (file.startsWith(".")) {
15
- return path.join(path.dirname(from), file);
16
- }
17
- return path.join(this.#root, file);
18
- }
19
- }
@@ -1,13 +0,0 @@
1
- export type TokenType = "string" | "tag" | "filter" | "comment";
2
- export type Token = [TokenType, string, number?];
3
- export interface TokenizeResult {
4
- tokens: Token[];
5
- position: number;
6
- error: Error | undefined;
7
- }
8
- export default function tokenize(source: string): TokenizeResult;
9
- /**
10
- * Parse a tag and return the indexes of the start and end brackets, and the filters between.
11
- * For example: {{ tag |> filter1 |> filter2 }} => [2, 9, 20, 31]
12
- */
13
- export declare function parseTag(source: string): number[];
@@ -1,221 +0,0 @@
1
- export default function tokenize(source) {
2
- const tokens = [];
3
- let type = "string";
4
- let position = 0;
5
- try {
6
- while (source.length > 0) {
7
- if (type === "string") {
8
- const index = source.indexOf("{{");
9
- const code = index === -1 ? source : source.slice(0, index);
10
- tokens.push([type, code, position]);
11
- if (index === -1) {
12
- break;
13
- }
14
- position += index;
15
- source = source.slice(index);
16
- type = source.startsWith("{{#") ? "comment" : "tag";
17
- continue;
18
- }
19
- if (type === "comment") {
20
- source = source.slice(3);
21
- const index = source.indexOf("#}}");
22
- const comment = index === -1 ? source : source.slice(0, index);
23
- tokens.push([type, comment, position]);
24
- if (index === -1) {
25
- break;
26
- }
27
- position += index + 3;
28
- source = source.slice(index + 3);
29
- type = "string";
30
- continue;
31
- }
32
- if (type === "tag") {
33
- const indexes = parseTag(source);
34
- const lastIndex = indexes.length - 1;
35
- let tag;
36
- indexes.reduce((prev, curr, index) => {
37
- const code = source.slice(prev, curr - 2);
38
- // Tag
39
- if (index === 1) {
40
- tag = [type, code, position];
41
- tokens.push(tag);
42
- return curr;
43
- }
44
- // Filters
45
- tokens.push(["filter", code]);
46
- return curr;
47
- });
48
- position += indexes[lastIndex];
49
- source = source.slice(indexes[lastIndex]);
50
- type = "string";
51
- // Search the closing echo tag {{ /echo }}
52
- if (tag?.[1].match(/^\-?\s*echo\s*\-?$/)) {
53
- const end = source.match(/{{\-?\s*\/echo\s*\-?}}/);
54
- if (!end) {
55
- throw new Error("Unclosed echo tag");
56
- }
57
- const rawCode = source.slice(0, end.index);
58
- tag[1] = `echo ${JSON.stringify(rawCode)}`;
59
- const length = Number(end.index) + end[0].length;
60
- source = source.slice(length);
61
- position += length;
62
- }
63
- continue;
64
- }
65
- }
66
- }
67
- catch (error) {
68
- return { tokens, position, error };
69
- }
70
- return { tokens, position, error: undefined };
71
- }
72
- /**
73
- * Parse a tag and return the indexes of the start and end brackets, and the filters between.
74
- * For example: {{ tag |> filter1 |> filter2 }} => [2, 9, 20, 31]
75
- */
76
- export function parseTag(source) {
77
- const length = source.length;
78
- const statuses = [];
79
- const indexes = [2];
80
- let index = 0;
81
- while (index < length) {
82
- const char = source.charAt(index++);
83
- switch (char) {
84
- // Detect start brackets
85
- case "{": {
86
- const status = statuses[0];
87
- if (status === "literal" && source.charAt(index - 2) === "$") {
88
- statuses.unshift("bracket");
89
- }
90
- else if (status !== "comment" && status !== "single-quote" &&
91
- status !== "double-quote" && status !== "literal" &&
92
- status !== "regex" && status !== "line-comment") {
93
- statuses.unshift("bracket");
94
- }
95
- break;
96
- }
97
- // Detect end brackets
98
- case "}": {
99
- const status = statuses[0];
100
- if (status === "bracket") {
101
- statuses.shift();
102
- if (statuses.length === 0) {
103
- indexes.push(index);
104
- return indexes;
105
- }
106
- }
107
- break;
108
- }
109
- // Detect double quotes
110
- case '"': {
111
- const status = statuses[0];
112
- if (status === "double-quote") {
113
- statuses.shift();
114
- }
115
- else if (status !== "comment" &&
116
- status !== "single-quote" &&
117
- status !== "literal" &&
118
- status !== "regex" &&
119
- status !== "line-comment") {
120
- statuses.unshift("double-quote");
121
- }
122
- break;
123
- }
124
- // Detect single quotes
125
- case "'": {
126
- const status = statuses[0];
127
- if (status === "single-quote") {
128
- statuses.shift();
129
- }
130
- else if (status !== "comment" &&
131
- status !== "double-quote" &&
132
- status !== "literal" &&
133
- status !== "regex" &&
134
- status !== "line-comment") {
135
- statuses.unshift("single-quote");
136
- }
137
- break;
138
- }
139
- // Detect literals
140
- case "`": {
141
- const status = statuses[0];
142
- if (status === "literal") {
143
- statuses.shift();
144
- }
145
- else if (status !== "comment" &&
146
- status !== "double-quote" &&
147
- status !== "single-quote" &&
148
- status !== "regex" &&
149
- status !== "line-comment") {
150
- statuses.unshift("literal");
151
- }
152
- break;
153
- }
154
- // Detect comments and regex
155
- case "/": {
156
- const status = statuses[0];
157
- if (status === "single-quote" || status === "double-quote" ||
158
- status === "literal" || status === "line-comment") {
159
- break;
160
- }
161
- // We are in a comment: close or ignore
162
- if (status === "comment") {
163
- if (source.charAt(index - 2) === "*") {
164
- statuses.shift();
165
- }
166
- break;
167
- }
168
- // We are in a regex: close or ignore
169
- if (status === "regex") {
170
- if (source.charAt(index - 2) !== "\\") {
171
- statuses.shift();
172
- }
173
- break;
174
- }
175
- // Start a new comment
176
- if (source.charAt(index) === "*") {
177
- statuses.unshift("comment");
178
- break;
179
- }
180
- // Start a new line comment
181
- if (source.charAt(index - 2) === "/") {
182
- statuses.unshift("line-comment");
183
- break;
184
- }
185
- // Start a new regex
186
- const prev = prevChar(source, index - 1);
187
- if (prev === "(" || prev === "=" || prev === ":" || prev === ",") {
188
- statuses.unshift("regex");
189
- }
190
- break;
191
- }
192
- // Detect end of line comments
193
- case "\n": {
194
- const status = statuses[0];
195
- if (status === "line-comment") {
196
- statuses.shift();
197
- }
198
- break;
199
- }
200
- // Detect filters
201
- case "|": {
202
- const status = statuses[0];
203
- if (status === "bracket" && source.charAt(index) === ">") {
204
- indexes.push(index + 1);
205
- }
206
- break;
207
- }
208
- }
209
- }
210
- throw new Error("Unclosed tag");
211
- }
212
- // Get the previous character in a string ignoring spaces, line breaks and tabs
213
- function prevChar(source, index) {
214
- while (index > 0) {
215
- const char = source.charAt(--index);
216
- if (char !== " " && char !== "\n" && char !== "\r" && char !== "\t") {
217
- return char;
218
- }
219
- }
220
- return "";
221
- }
@@ -1 +0,0 @@
1
- export declare function transformTemplateCode(code: string, templateState: string): string;