@gregorlohaus/tdir 0.1.8 → 0.1.9

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/README.md CHANGED
@@ -61,6 +61,8 @@ render("./output", {
61
61
  | `<@if(context.x)>` | Conditional block — boolean check (must end with `<@endif>`) |
62
62
  | `<@if(eq(context.x,"value"))>` | Conditional block — string equality check |
63
63
  | `<@if(neq(context.x,"value"))>` | Conditional block — string inequality check |
64
+ | `<@if(and(context.x,eq(context.y,"value")))>` | Conditional block — all child conditions must match |
65
+ | `<@if(or(context.x,eq(context.y,"value")))>` | Conditional block — any child condition may match |
64
66
  | `<@elseif(context.y)>` | Else-if branch (same forms as `@if`) |
65
67
  | `<@else>` | Else branch |
66
68
  | `<@endif>` | End conditional block |
@@ -74,6 +76,8 @@ render("./output", {
74
76
  | `<@if(context.x)>dirname` | Conditionally include directory/file (boolean check) |
75
77
  | `<@if(eq(context.x,"value"))>dirname` | Conditionally include by string equality |
76
78
  | `<@if(neq(context.x,"value"))>dirname` | Conditionally include by string inequality |
79
+ | `<@if(and(context.x,eq(context.y,"value")))>dirname` | Conditionally include by combined conditions |
80
+ | `<@if(or(context.x,eq(context.y,"value")))>dirname` | Conditionally include by alternate conditions |
77
81
  | `<@var(context.x)>` | Dynamic directory/file name |
78
82
 
79
83
  These can be combined: `<@if(context.web.create)><@var(context.web.dir)>` creates a directory named by `context.web.dir` only if `context.web.create` is true.
package/dist/index.js CHANGED
@@ -5,14 +5,120 @@ import { z } from "zod";
5
5
  import { readdirSync, statSync, readFileSync } from "node:fs";
6
6
  import { join } from "node:path";
7
7
  import { TextDecoder } from "node:util";
8
- var IF_RE = /<@(?:if|elseif)\((.+?)\)>/g;
8
+
9
+ // scanner.ts
10
+ function readCondition(text, start) {
11
+ let depth = 0;
12
+ let inString = false;
13
+ let escaped = false;
14
+ for (let i = start;i < text.length; i++) {
15
+ const char = text[i];
16
+ if (escaped) {
17
+ escaped = false;
18
+ continue;
19
+ }
20
+ if (char === "\\") {
21
+ escaped = true;
22
+ continue;
23
+ }
24
+ if (char === '"') {
25
+ inString = !inString;
26
+ continue;
27
+ }
28
+ if (inString)
29
+ continue;
30
+ if (char === "(") {
31
+ depth += 1;
32
+ continue;
33
+ }
34
+ if (char === ")") {
35
+ depth -= 1;
36
+ if (depth === 0 && text[i + 1] === ">") {
37
+ return { condition: text.slice(start + 1, i), end: i + 2 };
38
+ }
39
+ }
40
+ }
41
+ return null;
42
+ }
43
+ function getDirectiveTokens(text) {
44
+ const tokens = [];
45
+ for (let i = 0;i < text.length; i++) {
46
+ if (text[i] !== "<" || text[i + 1] !== "@")
47
+ continue;
48
+ const rest = text.slice(i + 2);
49
+ const type = ["elseif", "endif", "else", "if"].find((name) => rest.startsWith(name));
50
+ if (!type)
51
+ continue;
52
+ const afterName = i + 2 + type.length;
53
+ if ((type === "if" || type === "elseif") && text[afterName] === "(") {
54
+ const parsed = readCondition(text, afterName);
55
+ if (!parsed)
56
+ continue;
57
+ tokens.push({ type, condition: parsed.condition, index: i, end: parsed.end });
58
+ i = parsed.end - 1;
59
+ } else if ((type === "else" || type === "endif") && text[afterName] === ">") {
60
+ tokens.push({ type, index: i, end: afterName + 1 });
61
+ i = afterName;
62
+ }
63
+ }
64
+ return tokens;
65
+ }
66
+ function splitArgs(args) {
67
+ const result = [];
68
+ let current = "";
69
+ let depth = 0;
70
+ let inString = false;
71
+ let escaped = false;
72
+ for (const char of args) {
73
+ if (escaped) {
74
+ current += char;
75
+ escaped = false;
76
+ continue;
77
+ }
78
+ if (char === "\\") {
79
+ current += char;
80
+ escaped = true;
81
+ continue;
82
+ }
83
+ if (char === '"') {
84
+ current += char;
85
+ inString = !inString;
86
+ continue;
87
+ }
88
+ if (!inString && char === "(")
89
+ depth += 1;
90
+ if (!inString && char === ")")
91
+ depth -= 1;
92
+ if (!inString && depth === 0 && char === ",") {
93
+ result.push(current.trim());
94
+ current = "";
95
+ continue;
96
+ }
97
+ current += char;
98
+ }
99
+ if (current.trim() !== "")
100
+ result.push(current.trim());
101
+ return result;
102
+ }
103
+
104
+ // parser.ts
9
105
  var VAR_RE = /<@var\(context\.(.+?)(?::(\w+))?\)>/g;
10
- var DIRECTIVE_RE = /<@(if|elseif|else|endif)(?:\((.+?)\))?>/g;
11
106
  var STRING_COMPARE_RE = /^(?:eq|neq)\(context\.(.+?),\s*"(.*)"\)$/;
12
107
  var PATH_RE = /^context\.(.+)$/;
13
108
  function extractCondition(expr, vars) {
14
109
  if (!expr)
15
110
  throw new Error("Missing condition expression");
111
+ for (const operator of ["and", "or"]) {
112
+ const prefix = `${operator}(`;
113
+ if (expr.startsWith(prefix) && expr.endsWith(")")) {
114
+ const args = splitArgs(expr.slice(prefix.length, -1));
115
+ if (args.length === 0)
116
+ throw new Error(`Invalid condition expression: ${expr}`);
117
+ for (const arg of args)
118
+ extractCondition(arg, vars);
119
+ return;
120
+ }
121
+ }
16
122
  const stringCompareMatch = expr.match(STRING_COMPARE_RE);
17
123
  if (stringCompareMatch) {
18
124
  vars.push({ path: stringCompareMatch[1], type: "string" });
@@ -26,8 +132,10 @@ function extractCondition(expr, vars) {
26
132
  throw new Error(`Invalid condition expression: ${expr}`);
27
133
  }
28
134
  function extractFromString(text, vars) {
29
- for (const match of text.matchAll(IF_RE)) {
30
- extractCondition(match[1], vars);
135
+ for (const token of getDirectiveTokens(text)) {
136
+ if (token.type === "if" || token.type === "elseif") {
137
+ extractCondition(token.condition, vars);
138
+ }
31
139
  }
32
140
  for (const match of text.matchAll(VAR_RE)) {
33
141
  vars.push({ path: match[1], type: match[2] ?? "string" });
@@ -35,9 +143,9 @@ function extractFromString(text, vars) {
35
143
  }
36
144
  function validateIfBlocks(content, vars) {
37
145
  const stack = [];
38
- for (const match of content.matchAll(DIRECTIVE_RE)) {
39
- const directive = match[1];
40
- const condition = match[2];
146
+ for (const token of getDirectiveTokens(content)) {
147
+ const directive = token.type;
148
+ const condition = token.condition;
41
149
  if (directive === "if") {
42
150
  extractCondition(condition, vars);
43
151
  stack.push({ sawElse: false });
@@ -121,12 +229,20 @@ import { homedir } from "node:os";
121
229
  import { TextDecoder as TextDecoder2 } from "node:util";
122
230
  var IF_PATH_RE = /^<@if\((.+?)\)>(.*)$/;
123
231
  var VAR_RE2 = /<@var\(context\.(.+?)(?::(\w+))?\)>/g;
124
- var DIRECTIVE_RE2 = /<@(if|elseif|else|endif)(?:\((.+?)\))?>/g;
125
232
  var STRING_COMPARE_RE2 = /^(eq|neq)\(context\.(.+?),\s*"(.*)"\)$/;
126
233
  var PATH_RE2 = /^context\.(.+)$/;
127
234
  function evalCondition(expr, context) {
128
235
  if (!expr)
129
236
  throw new Error("Missing condition expression");
237
+ for (const operator of ["and", "or"]) {
238
+ const prefix = `${operator}(`;
239
+ if (expr.startsWith(prefix) && expr.endsWith(")")) {
240
+ const args = splitArgs(expr.slice(prefix.length, -1));
241
+ if (args.length === 0)
242
+ throw new Error(`Invalid condition expression: ${expr}`);
243
+ return operator === "and" ? args.every((arg) => evalCondition(arg, context)) : args.some((arg) => evalCondition(arg, context));
244
+ }
245
+ }
130
246
  const stringCompareMatch = expr.match(STRING_COMPARE_RE2);
131
247
  if (stringCompareMatch) {
132
248
  const result = resolveContext(context, stringCompareMatch[2]) === stringCompareMatch[3];
@@ -240,14 +356,6 @@ function isUtf8Text2(buffer) {
240
356
  return false;
241
357
  }
242
358
  }
243
- function getDirectiveTokens(content) {
244
- return Array.from(content.matchAll(DIRECTIVE_RE2), (match) => ({
245
- type: match[1],
246
- condition: match[2],
247
- index: match.index,
248
- end: match.index + match[0].length
249
- }));
250
- }
251
359
  function parseNodes(content, tokens, tokenIndex, pos, stopTypes) {
252
360
  const nodes = [];
253
361
  while (tokenIndex < tokens.length) {
@@ -0,0 +1,8 @@
1
+ export type DirectiveToken = {
2
+ type: "if" | "elseif" | "else" | "endif";
3
+ condition?: string;
4
+ index: number;
5
+ end: number;
6
+ };
7
+ export declare function getDirectiveTokens(text: string): DirectiveToken[];
8
+ export declare function splitArgs(args: string): string[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gregorlohaus/tdir",
3
- "version": "0.1.8",
3
+ "version": "0.1.9",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",