ventojs 0.7.3 → 0.8.0

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/esm/mod.d.ts CHANGED
@@ -3,5 +3,6 @@ import { Loader } from "./src/loader.js";
3
3
  export interface Options {
4
4
  includes?: string | Loader;
5
5
  dataVarname?: string;
6
+ autoescape?: boolean;
6
7
  }
7
8
  export default function (options?: Options): Environment;
package/esm/mod.js CHANGED
@@ -10,7 +10,9 @@ import layoutTag from "./plugins/layout.js";
10
10
  import functionTag from "./plugins/function.js";
11
11
  import importTag from "./plugins/import.js";
12
12
  import exportTag from "./plugins/export.js";
13
+ import echoTag from "./plugins/echo.js";
13
14
  import escape from "./plugins/escape.js";
15
+ import unescape from "./plugins/unescape.js";
14
16
  export default function (options = {}) {
15
17
  const loader = typeof options.includes === "object"
16
18
  ? options.includes
@@ -18,6 +20,7 @@ export default function (options = {}) {
18
20
  const env = new Environment({
19
21
  loader,
20
22
  dataVarname: options.dataVarname || "it",
23
+ autoescape: options.autoescape || false,
21
24
  });
22
25
  // Register basic plugins
23
26
  env.use(ifTag());
@@ -29,6 +32,8 @@ export default function (options = {}) {
29
32
  env.use(functionTag());
30
33
  env.use(importTag());
31
34
  env.use(exportTag());
35
+ env.use(echoTag());
32
36
  env.use(escape());
37
+ env.use(unescape());
33
38
  return env;
34
39
  }
@@ -0,0 +1,2 @@
1
+ import type { Environment } from "../src/environment.js";
2
+ export default function (): (env: Environment) => void;
@@ -0,0 +1,13 @@
1
+ export default function () {
2
+ return (env) => {
3
+ env.tags.push(setTag);
4
+ };
5
+ }
6
+ function setTag(env, code, output, tokens) {
7
+ if (!code.startsWith("echo ")) {
8
+ return;
9
+ }
10
+ const value = code.replace(/^echo\s+/, "");
11
+ const val = env.compileFilters(tokens, value, env.options.autoescape);
12
+ return `${output} += ${val};`;
13
+ }
@@ -3,7 +3,7 @@ export default function () {
3
3
  env.tags.push(includeTag);
4
4
  };
5
5
  }
6
- function includeTag(_env, code, output) {
6
+ function includeTag(env, code, output, tokens) {
7
7
  if (!code.startsWith("include ")) {
8
8
  return;
9
9
  }
@@ -17,6 +17,6 @@ function includeTag(_env, code, output) {
17
17
  {...__data${data ? `, ${data}` : ""}},
18
18
  __file
19
19
  );
20
- ${output} += __tmp.content;
20
+ ${output} += ${env.compileFilters(tokens, "__tmp.content")};
21
21
  }`;
22
22
  }
@@ -24,7 +24,7 @@ function layoutTag(env, code, output, tokens) {
24
24
  tokens.shift();
25
25
  compiled.push(`${varname} = ${compiledFilters};`);
26
26
  compiled.push(`__tmp = await __env.run(${file},
27
- {...__data${data ? `, ${data}` : ""}, content: ${varname}},
27
+ {...__data${data ? `, ${data}` : ""}, content: ${env.compileFilters(tokens, varname)}},
28
28
  __file
29
29
  );
30
30
  ${output} += __tmp.content;`);
@@ -0,0 +1,2 @@
1
+ import type { Environment } from "../src/environment.js";
2
+ export default function (): (env: Environment) => void;
@@ -0,0 +1,16 @@
1
+ export default function () {
2
+ return (env) => {
3
+ env.filters.unescape = unescape;
4
+ };
5
+ }
6
+ const unescapeMap = {
7
+ "&": "&",
8
+ "&lt;": "<",
9
+ "&gt;": ">",
10
+ "&quot;": '"',
11
+ "&#39;": "'",
12
+ "&#x60;": "`",
13
+ };
14
+ function unescape(str) {
15
+ return str.replace(/(&amp;|&lt;|&gt;|&quot;|&#39;|&#x60;)/g, (match) => unescapeMap[match]);
16
+ }
@@ -19,7 +19,8 @@ export type Filter = (...args: any[]) => any;
19
19
  export type Plugin = (env: Environment) => void;
20
20
  export interface Options {
21
21
  loader: Loader;
22
- dataVarname?: string;
22
+ dataVarname: string;
23
+ autoescape: boolean;
23
24
  }
24
25
  export declare class Environment {
25
26
  cache: Map<string, Template>;
@@ -36,5 +37,5 @@ export declare class Environment {
36
37
  compile(source: string, path?: string, defaults?: Record<string, unknown>, sync?: true): TemplateSync;
37
38
  load(file: string, from?: string): Promise<Template>;
38
39
  compileTokens(tokens: Token[], outputVar?: string, stopAt?: string[]): string[];
39
- compileFilters(tokens: Token[], output: string): string;
40
+ compileFilters(tokens: Token[], output: string, autoescape?: boolean): string;
40
41
  }
@@ -67,9 +67,9 @@ export class Environment {
67
67
  if (!this.cache.has(path)) {
68
68
  const { source, data } = await this.options.loader.load(path);
69
69
  const template = this.compile(source, path, data);
70
- this.cache.set(file, template);
70
+ this.cache.set(path, template);
71
71
  }
72
- return this.cache.get(file);
72
+ return this.cache.get(path);
73
73
  }
74
74
  compileTokens(tokens, outputVar = "__exports.content", stopAt) {
75
75
  const compiled = [];
@@ -81,7 +81,7 @@ export class Environment {
81
81
  if (type === "comment") {
82
82
  continue;
83
83
  }
84
- if (type === "string" || type === "raw") {
84
+ if (type === "string") {
85
85
  compiled.push(`${outputVar} += ${JSON.stringify(code)};`);
86
86
  continue;
87
87
  }
@@ -94,7 +94,7 @@ export class Environment {
94
94
  }
95
95
  }
96
96
  // Unknown tag, just print it
97
- const expression = this.compileFilters(tokens, code);
97
+ const expression = this.compileFilters(tokens, code, this.options.autoescape);
98
98
  compiled.push(`${outputVar} += (${expression}) ?? "";`);
99
99
  continue;
100
100
  }
@@ -102,7 +102,8 @@ export class Environment {
102
102
  }
103
103
  return compiled;
104
104
  }
105
- compileFilters(tokens, output) {
105
+ compileFilters(tokens, output, autoescape = false) {
106
+ let unescaped = false;
106
107
  while (tokens.length > 0 && tokens[0][0] === "filter") {
107
108
  const [, code] = tokens.shift();
108
109
  const match = code.match(/^(await\s+)?([\w.]+)(?:\((.*)\))?$/);
@@ -121,10 +122,17 @@ export class Environment {
121
122
  }
122
123
  }
123
124
  else {
125
+ if (name === "unescape") {
126
+ unescaped = true;
127
+ }
124
128
  // It's a filter (e.g. filters.upper())
125
129
  output = `${isAsync ? "await " : ""}__env.filters.${name}(${output}${args ? `, ${args}` : ""})`;
126
130
  }
127
131
  }
132
+ // Escape by default
133
+ if (autoescape && !unescaped) {
134
+ output = `__env.filters.escape(${output})`;
135
+ }
128
136
  return output;
129
137
  }
130
138
  }
@@ -1,4 +1,4 @@
1
- export type TokenType = "string" | "tag" | "filter" | "comment" | "raw";
1
+ export type TokenType = "string" | "tag" | "filter" | "comment";
2
2
  export type Token = [TokenType, string];
3
3
  export default function tokenize(source: string): Token[];
4
4
  /**
@@ -17,15 +17,6 @@ export default function tokenize(source) {
17
17
  break;
18
18
  }
19
19
  source = source.slice(index);
20
- // Check if it's a {{raw}} tag
21
- const raw = parseRawTag(source);
22
- if (raw) {
23
- const rawCode = source.slice(raw[0], raw[1]);
24
- tokens.push(["raw", rawCode]);
25
- source = source.slice(raw[2]);
26
- type = "string";
27
- continue;
28
- }
29
20
  type = source.startsWith("{{#") ? "comment" : "tag";
30
21
  continue;
31
22
  }
@@ -44,6 +35,7 @@ export default function tokenize(source) {
44
35
  if (type === "tag") {
45
36
  const indexes = parseTag(source);
46
37
  const lastIndex = indexes.length - 1;
38
+ let tag;
47
39
  indexes.reduce((prev, curr, index) => {
48
40
  let code = source.slice(prev, curr - 2);
49
41
  // Tag
@@ -59,7 +51,8 @@ export default function tokenize(source) {
59
51
  code = code.slice(0, -1);
60
52
  trimNext = true;
61
53
  }
62
- tokens.push([type, code.trim()]);
54
+ tag = [type, code.trim()];
55
+ tokens.push(tag);
63
56
  return curr;
64
57
  }
65
58
  // Right trim
@@ -73,6 +66,16 @@ export default function tokenize(source) {
73
66
  });
74
67
  source = source.slice(indexes[indexes.length - 1]);
75
68
  type = "string";
69
+ // Search the closing echo tag {{ /echo }}
70
+ if (tag?.[1] === "echo") {
71
+ const end = source.match(/{{\s*\/echo\s*}}/);
72
+ if (!end) {
73
+ throw new Error("Unclosed echo tag");
74
+ }
75
+ const rawCode = source.slice(0, end.index);
76
+ tag[1] = `echo ${JSON.stringify(rawCode)}`;
77
+ source = source.slice(Number(end.index) + end[0].length);
78
+ }
76
79
  continue;
77
80
  }
78
81
  }
@@ -180,18 +183,3 @@ export function parseTag(source) {
180
183
  }
181
184
  throw new Error("Unclosed tag");
182
185
  }
183
- function parseRawTag(source) {
184
- const startResult = source.match(/^{{\s*raw\s*}}/);
185
- if (!startResult) {
186
- return;
187
- }
188
- const endResult = source.match(/{{\s*\/raw\s*}}/);
189
- if (!endResult) {
190
- throw new Error("Unclosed raw tag");
191
- }
192
- return [
193
- startResult[0].length,
194
- endResult.index,
195
- endResult.index + endResult[0].length,
196
- ];
197
- }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "module": "./esm/mod.js",
3
3
  "name": "ventojs",
4
- "version": "0.7.3",
4
+ "version": "0.8.0",
5
5
  "description": "🌬 A minimal but powerful template engine",
6
6
  "license": "MIT",
7
7
  "repository": "github:oscarotero/vento",