@gregorlohaus/tdir 0.1.1 → 0.1.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.
Files changed (3) hide show
  1. package/README.md +9 -3
  2. package/dist/index.js +39 -13
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -58,8 +58,9 @@ render("./output", {
58
58
 
59
59
  | Directive | Description |
60
60
  |---|---|
61
- | `<@if(context.x)>` | Conditional block (must end with `<@endif>`) |
62
- | `<@elseif(context.y)>` | Else-if branch |
61
+ | `<@if(context.x)>` | Conditional block — truthy check (must end with `<@endif>`) |
62
+ | `<@if(eq(context.x,"value"))>` | Conditional block — string equality check |
63
+ | `<@elseif(context.y)>` | Else-if branch (same forms as `@if`) |
63
64
  | `<@else>` | Else branch |
64
65
  | `<@endif>` | End conditional block |
65
66
  | `<@var(context.x)>` | Substitute with context value (default type: `string`) |
@@ -69,7 +70,8 @@ render("./output", {
69
70
 
70
71
  | Directive | Description |
71
72
  |---|---|
72
- | `<@if(context.x)>dirname` | Conditionally include directory/file |
73
+ | `<@if(context.x)>dirname` | Conditionally include directory/file (truthy check) |
74
+ | `<@if(eq(context.x,"value"))>dirname` | Conditionally include by string equality |
73
75
  | `<@var(context.x)>` | Dynamic directory/file name |
74
76
 
75
77
  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.
@@ -117,6 +119,10 @@ render("./output", { web: "not a boolean", header: { show: true, title: "Hi" } }
117
119
  // ZodError: expected boolean, received string at "web"
118
120
  ```
119
121
 
122
+ ## Re-rendering
123
+
124
+ `render(target, context)` clears `target` before writing, so rendering the same template into the same directory with different contexts always produces a clean result (files/paths excluded by conditionals won't linger from a previous run).
125
+
120
126
  ## Unmatched directives
121
127
 
122
128
  A `<@if>` without a matching `<@endif>` throws at render time:
package/dist/index.js CHANGED
@@ -1,14 +1,24 @@
1
- // index.ts
2
- import { z } from "zod";
3
-
4
1
  // parser.ts
5
2
  import { readdirSync, statSync, readFileSync } from "node:fs";
6
3
  import { join } from "node:path";
7
- var IF_RE = /<@if\(context\.(.+?)\)>/g;
4
+ var IF_RE = /<@(?:if|elseif)\((.+?)\)>/g;
8
5
  var VAR_RE = /<@var\(context\.(.+?)(?::(\w+))?\)>/g;
6
+ var EQ_RE = /^eq\(context\.(.+?),\s*"(.*)"\)$/;
7
+ var PATH_RE = /^context\.(.+)$/;
8
+ function extractCondition(expr, vars) {
9
+ const eqMatch = expr.match(EQ_RE);
10
+ if (eqMatch) {
11
+ vars.push({ path: eqMatch[1], type: "string" });
12
+ return;
13
+ }
14
+ const pathMatch = expr.match(PATH_RE);
15
+ if (pathMatch) {
16
+ vars.push({ path: pathMatch[1], type: "boolean" });
17
+ }
18
+ }
9
19
  function extractFromString(text, vars) {
10
20
  for (const match of text.matchAll(IF_RE)) {
11
- vars.push({ path: match[1], type: "boolean" });
21
+ extractCondition(match[1], vars);
12
22
  }
13
23
  for (const match of text.matchAll(VAR_RE)) {
14
24
  vars.push({ path: match[1], type: match[2] ?? "string" });
@@ -42,11 +52,24 @@ function parse(dirPath) {
42
52
  }
43
53
 
44
54
  // render.ts
45
- import { readdirSync as readdirSync2, statSync as statSync2, readFileSync as readFileSync2, mkdirSync, writeFileSync } from "node:fs";
55
+ import { readdirSync as readdirSync2, statSync as statSync2, readFileSync as readFileSync2, mkdirSync, writeFileSync, rmSync } from "node:fs";
46
56
  import { join as join2 } from "node:path";
47
- var IF_PATH_RE = /^<@if\(context\.(.+?)\)>(.*)$/;
57
+ var IF_PATH_RE = /^<@if\((.+?)\)>(.*)$/;
48
58
  var VAR_RE2 = /<@var\(context\.(.+?)(?::(\w+))?\)>/g;
49
- var DIRECTIVE_RE = /<@(if|elseif|else|endif)(?:\(context\.(.+?)\))?>/g;
59
+ var DIRECTIVE_RE = /<@(if|elseif|else|endif)(?:\((.+?)\))?>/g;
60
+ var EQ_RE2 = /^eq\(context\.(.+?),\s*"(.*)"\)$/;
61
+ var PATH_RE2 = /^context\.(.+)$/;
62
+ function evalCondition(expr, context) {
63
+ const eqMatch = expr.match(EQ_RE2);
64
+ if (eqMatch) {
65
+ return resolve(context, eqMatch[1]) === eqMatch[2];
66
+ }
67
+ const pathMatch = expr.match(PATH_RE2);
68
+ if (pathMatch) {
69
+ return !!resolve(context, pathMatch[1]);
70
+ }
71
+ throw new Error(`Invalid condition expression: ${expr}`);
72
+ }
50
73
  function resolve(context, path) {
51
74
  const segments = path.split(".");
52
75
  let current = context;
@@ -72,7 +95,7 @@ function processIfBlocks(content, context) {
72
95
  if (directive === "if") {
73
96
  if (isEmitting())
74
97
  result += content.slice(pos, match.index);
75
- const truthy = !!resolve(context, condPath);
98
+ const truthy = evalCondition(condPath, context);
76
99
  stack.push({ matched: truthy, active: truthy });
77
100
  pos = re.lastIndex;
78
101
  } else if (directive === "elseif") {
@@ -84,7 +107,7 @@ function processIfBlocks(content, context) {
84
107
  if (top.matched) {
85
108
  top.active = false;
86
109
  } else {
87
- const truthy = !!resolve(context, condPath);
110
+ const truthy = evalCondition(condPath, context);
88
111
  top.matched = truthy;
89
112
  top.active = truthy;
90
113
  }
@@ -120,6 +143,10 @@ function renderContent(content, context) {
120
143
  });
121
144
  }
122
145
  function renderDir(srcDir, destDir, context) {
146
+ rmSync(destDir, { recursive: true, force: true });
147
+ renderDirInner(srcDir, destDir, context);
148
+ }
149
+ function renderDirInner(srcDir, destDir, context) {
123
150
  mkdirSync(destDir, { recursive: true });
124
151
  const entries = readdirSync2(srcDir).sort();
125
152
  for (const entry of entries) {
@@ -128,8 +155,7 @@ function renderDir(srcDir, destDir, context) {
128
155
  const ifMatch = entry.match(IF_PATH_RE);
129
156
  let outputName = entry;
130
157
  if (ifMatch) {
131
- const conditionPath = ifMatch[1];
132
- if (!resolve(context, conditionPath))
158
+ if (!evalCondition(ifMatch[1], context))
133
159
  continue;
134
160
  outputName = ifMatch[2];
135
161
  }
@@ -138,7 +164,7 @@ function renderDir(srcDir, destDir, context) {
138
164
  });
139
165
  const destPath = join2(destDir, outputName);
140
166
  if (stat.isDirectory()) {
141
- renderDir(srcPath, destPath, context);
167
+ renderDirInner(srcPath, destPath, context);
142
168
  } else {
143
169
  mkdirSync(destDir, { recursive: true });
144
170
  const content = readFileSync2(srcPath, "utf-8");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gregorlohaus/tdir",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",