@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.
- package/README.md +9 -3
- package/dist/index.js +39 -13
- 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
|
-
| `<@
|
|
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\(
|
|
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
|
-
|
|
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\(
|
|
57
|
+
var IF_PATH_RE = /^<@if\((.+?)\)>(.*)$/;
|
|
48
58
|
var VAR_RE2 = /<@var\(context\.(.+?)(?::(\w+))?\)>/g;
|
|
49
|
-
var DIRECTIVE_RE = /<@(if|elseif|else|endif)(?:\(
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
167
|
+
renderDirInner(srcPath, destPath, context);
|
|
142
168
|
} else {
|
|
143
169
|
mkdirSync(destDir, { recursive: true });
|
|
144
170
|
const content = readFileSync2(srcPath, "utf-8");
|