saladplate 0.1.2 → 0.2.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/README.md +28 -14
- package/dist/bin/saladplate.js +219 -151
- package/dist/src/index.js +87 -77
- package/package.json +8 -5
- package/dist/bin/saladplate.d.ts +0 -2
- package/dist/src/index.d.ts +0 -16
package/README.md
CHANGED
|
@@ -2,20 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
_Simple templating; it's smaller than usual._
|
|
4
4
|
|
|
5
|
-
Saladplate is a very simple templating tool, with
|
|
5
|
+
Saladplate is a very simple templating tool, with 4 features:
|
|
6
6
|
|
|
7
7
|
1. Replace `${{ VAR }}` with the contents of the environment variable `VAR`.
|
|
8
8
|
2. Replace `$<< filename >>` with the contents of the file `filename`.
|
|
9
|
-
3. Replace
|
|
9
|
+
3. Replace `$^^ filename ^^` _and everything after it_ with the contents of the file `filename`,
|
|
10
|
+
replacing `^^` in _that_ content with the same "everything after it".
|
|
11
|
+
4. Replace `$(( some command ))` with the output of the command `some command`.
|
|
10
12
|
|
|
11
|
-
If you need more functionality
|
|
13
|
+
If you need more functionality than that, this is not the templating tool for you.
|
|
12
14
|
|
|
13
15
|
## Installation
|
|
14
16
|
|
|
15
17
|
```bash
|
|
16
|
-
$ npm -g
|
|
18
|
+
$ npm install -g saladplate
|
|
17
19
|
$ saladplate --version
|
|
18
|
-
saladplate: version 0.
|
|
20
|
+
saladplate: version 0.2.0
|
|
19
21
|
```
|
|
20
22
|
|
|
21
23
|
## Usage
|
|
@@ -57,27 +59,39 @@ You can also use the exported `template` function to template a string:
|
|
|
57
59
|
|
|
58
60
|
```typescript
|
|
59
61
|
import { template } from "saladplate";
|
|
60
|
-
console.info(template("The current PATH is: $PATH"));
|
|
62
|
+
console.info(await template("The current PATH is: ${{ PATH }}"));
|
|
61
63
|
```
|
|
62
64
|
|
|
63
65
|
## Development
|
|
64
66
|
|
|
65
|
-
When developing locally, you can test your changes using the
|
|
66
|
-
which uses `
|
|
67
|
-
separate arguments to `
|
|
67
|
+
When developing locally, you can test your changes using the _script_ `saladplate`,
|
|
68
|
+
which uses `bun`. Note the use of `run` below, as well as the use of `--` to
|
|
69
|
+
separate arguments to `bun run` from arguments to `saladplate`:
|
|
68
70
|
|
|
69
71
|
```bash
|
|
70
|
-
$
|
|
72
|
+
$ bun run saladplate -- --version
|
|
73
|
+
saladplate: version 0.2.0
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Building and publishing
|
|
77
|
+
|
|
78
|
+
To build the distributable JS:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
$ bun run build
|
|
82
|
+
```
|
|
71
83
|
|
|
72
|
-
|
|
73
|
-
> ts-node ./bin/saladplate.ts
|
|
84
|
+
This will build to `dist/`. To publish to NPM:
|
|
74
85
|
|
|
75
|
-
|
|
86
|
+
```bash
|
|
87
|
+
$ npm publish
|
|
76
88
|
```
|
|
77
89
|
|
|
90
|
+
### Testing
|
|
91
|
+
|
|
78
92
|
Tests are located in `tests/` and consist of input templates `tests/*.test` and
|
|
79
93
|
corresponding expected outputs `tests/*.expected`. To run tests:
|
|
80
94
|
|
|
81
95
|
```bash
|
|
82
|
-
$
|
|
96
|
+
$ bun run test
|
|
83
97
|
```
|
package/dist/bin/saladplate.js
CHANGED
|
@@ -1,161 +1,229 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// @bun
|
|
3
|
+
|
|
4
|
+
// bin/saladplate.ts
|
|
5
|
+
import { open, readFile as readFile2, writeFile } from "fs/promises";
|
|
6
|
+
import path2 from "path";
|
|
7
|
+
import process2 from "process";
|
|
8
|
+
import { parseArgs } from "util";
|
|
9
|
+
|
|
10
|
+
// src/index.ts
|
|
11
|
+
import { exec as execCallback } from "node:child_process";
|
|
12
|
+
import { readFile } from "node:fs/promises";
|
|
13
|
+
import path from "node:path";
|
|
14
|
+
import process from "node:process";
|
|
15
|
+
import { promisify } from "node:util";
|
|
16
|
+
var exec = promisify(execCallback);
|
|
17
|
+
var BIN = "saladplate";
|
|
18
|
+
var cwdForFilename = (filename) => {
|
|
19
|
+
return filename === "/dev/stdin" ? process.cwd() : path.dirname(filename);
|
|
11
20
|
};
|
|
12
|
-
var
|
|
13
|
-
|
|
21
|
+
var VAR_RE = /\$\{\{([^\}]+)\}\}/g;
|
|
22
|
+
var FILE_RE = /\$\<\<([^\>]+)\>\>/g;
|
|
23
|
+
var EXEC_RE = /\$\(\(([^\)]+)\)\)/g;
|
|
24
|
+
var INJECT_RE = /\$\^\^([^\^]+)\^\^/;
|
|
25
|
+
async function replaceAsync(input, regexp, fn) {
|
|
26
|
+
const replacements = await Promise.all(Array.from(input.matchAll(regexp), (match) => fn(match)));
|
|
27
|
+
let i = 0;
|
|
28
|
+
return input.replace(regexp, () => replacements[i++]);
|
|
29
|
+
}
|
|
30
|
+
var replaceVar = (variable, filename, options) => {
|
|
31
|
+
options.debug && console.debug(`${BIN}: ${filename}: evaluating ${variable} for replacement...`);
|
|
32
|
+
return process.env[variable] ?? "";
|
|
33
|
+
};
|
|
34
|
+
var replaceFile = async (includeFilename, filename, options) => {
|
|
35
|
+
const cwd = cwdForFilename(filename);
|
|
36
|
+
const include = path.join(cwd, includeFilename);
|
|
37
|
+
options.debug && console.debug(`${BIN}: ${filename}: reading ${include} for replacement...`);
|
|
38
|
+
const content = await readFile(include, { encoding: "utf-8" });
|
|
39
|
+
return template(content, include, options);
|
|
40
|
+
};
|
|
41
|
+
var replaceExec = async (command, filename, options) => {
|
|
42
|
+
const cwd = cwdForFilename(filename);
|
|
43
|
+
options.debug && console.debug(`${BIN}: ${filename}: executing ${command} for replacement...`);
|
|
44
|
+
const { stdout } = await exec(command, { cwd });
|
|
45
|
+
return stdout;
|
|
14
46
|
};
|
|
15
|
-
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
47
|
+
var processInjections = async (content, filename, options) => {
|
|
48
|
+
const match = INJECT_RE.exec(content);
|
|
49
|
+
if (!match) {
|
|
50
|
+
return content;
|
|
51
|
+
}
|
|
52
|
+
const injectFilename = match[1].trim();
|
|
53
|
+
let contentBefore = content.slice(0, match.index);
|
|
54
|
+
const rest = content.slice(match.index + match[0].length);
|
|
55
|
+
const innerResult = await processInjections(rest, filename, options);
|
|
56
|
+
const cwd = cwdForFilename(filename);
|
|
57
|
+
const inject = path.join(cwd, injectFilename);
|
|
58
|
+
options.debug && console.debug(`${BIN}: ${filename}: reading ${inject} for injection...`);
|
|
59
|
+
const wrapperContent = await readFile(inject, { encoding: "utf-8" });
|
|
60
|
+
let pattern = "^^";
|
|
61
|
+
if (innerResult.startsWith(`
|
|
62
|
+
`) && wrapperContent.includes(`
|
|
63
|
+
^^`)) {
|
|
64
|
+
pattern = `
|
|
65
|
+
^^`;
|
|
66
|
+
}
|
|
67
|
+
if (innerResult.endsWith(`
|
|
68
|
+
`) && wrapperContent.includes(`^^
|
|
69
|
+
`)) {
|
|
70
|
+
pattern = pattern + `
|
|
71
|
+
`;
|
|
72
|
+
}
|
|
73
|
+
const wrapped = wrapperContent.replace(pattern, innerResult);
|
|
74
|
+
if (contentBefore.endsWith(`
|
|
75
|
+
`) && innerResult.startsWith(`
|
|
76
|
+
`)) {
|
|
77
|
+
contentBefore = contentBefore.slice(0, -1);
|
|
78
|
+
}
|
|
79
|
+
return contentBefore + wrapped;
|
|
80
|
+
};
|
|
81
|
+
var template = async (content, filename, options) => {
|
|
82
|
+
filename ??= "/dev/stdin";
|
|
83
|
+
options ??= {};
|
|
84
|
+
content = await replaceAsync(content, VAR_RE, (match) => {
|
|
85
|
+
return replaceVar(match[1].trim(), filename, options);
|
|
86
|
+
});
|
|
87
|
+
content = await replaceAsync(content, FILE_RE, (match) => {
|
|
88
|
+
return replaceFile(match[1].trim(), filename, options);
|
|
89
|
+
});
|
|
90
|
+
content = await replaceAsync(content, EXEC_RE, (match) => {
|
|
91
|
+
return replaceExec(match[1].trim(), filename, options);
|
|
92
|
+
});
|
|
93
|
+
content = await processInjections(content, filename, options);
|
|
94
|
+
content = content.replace(/\n+$/m, `
|
|
95
|
+
`);
|
|
96
|
+
return content;
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
// bin/saladplate.ts
|
|
100
|
+
var parseArgsConfig = {
|
|
101
|
+
args: process2.argv.slice(2),
|
|
102
|
+
options: {
|
|
103
|
+
debug: {
|
|
104
|
+
type: "boolean",
|
|
105
|
+
default: false,
|
|
106
|
+
description: "enable debug mode"
|
|
107
|
+
},
|
|
108
|
+
version: {
|
|
109
|
+
type: "boolean",
|
|
110
|
+
default: false,
|
|
111
|
+
short: "v",
|
|
112
|
+
description: "show version information"
|
|
57
113
|
},
|
|
58
|
-
|
|
114
|
+
help: {
|
|
115
|
+
type: "boolean",
|
|
116
|
+
default: false,
|
|
117
|
+
short: "h",
|
|
118
|
+
description: "show this help message"
|
|
119
|
+
},
|
|
120
|
+
directory: {
|
|
121
|
+
type: "string",
|
|
122
|
+
short: "d",
|
|
123
|
+
description: "output directory"
|
|
124
|
+
},
|
|
125
|
+
suffix: {
|
|
126
|
+
type: "string",
|
|
127
|
+
short: "s",
|
|
128
|
+
description: "output suffix; only applies when using --directory"
|
|
129
|
+
},
|
|
130
|
+
output: {
|
|
131
|
+
type: "string",
|
|
132
|
+
short: "o",
|
|
133
|
+
description: "output file; overrides --directory and --suffix"
|
|
134
|
+
}
|
|
135
|
+
},
|
|
136
|
+
allowPositionals: true
|
|
59
137
|
};
|
|
60
138
|
function help() {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
console.info("");
|
|
139
|
+
console.info(`Usage: ${BIN} [options] <file>...`);
|
|
140
|
+
console.info(`Options:`);
|
|
141
|
+
const entries = Object.entries(parseArgsConfig.options);
|
|
142
|
+
const prefixes = {};
|
|
143
|
+
entries.forEach(([name, option]) => {
|
|
144
|
+
const short = "short" in option ? `-${option.short}, ` : "";
|
|
145
|
+
const prefix = ` ${short}--${name}`;
|
|
146
|
+
prefixes[name] = prefix;
|
|
147
|
+
});
|
|
148
|
+
const len = Math.max(...entries.map(([name, option]) => prefixes[name].length)) + 2;
|
|
149
|
+
entries.forEach(([name, option]) => {
|
|
150
|
+
const prefix = prefixes[name];
|
|
151
|
+
console.info(`${prefix}${" ".repeat(len - prefix.length)}${option.description ?? ""}`);
|
|
152
|
+
});
|
|
153
|
+
console.info("");
|
|
77
154
|
}
|
|
155
|
+
var SALADPLATE_VERSION = "0.2.0";
|
|
78
156
|
function version() {
|
|
79
|
-
|
|
80
|
-
|
|
157
|
+
let version2 = SALADPLATE_VERSION;
|
|
158
|
+
if (version2.startsWith("${{")) {
|
|
159
|
+
version2 = process2.env.npm_package_version ?? "";
|
|
160
|
+
}
|
|
161
|
+
console.info(`${BIN}: version ${version2 || "unknown"}`);
|
|
81
162
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
163
|
+
var output = null;
|
|
164
|
+
async function outputForFilename(filename, options) {
|
|
165
|
+
if (output) {
|
|
166
|
+
return output;
|
|
167
|
+
}
|
|
168
|
+
if (options.output) {
|
|
169
|
+
output = {
|
|
170
|
+
file: await open(options.output, "w"),
|
|
171
|
+
filename: options.output
|
|
172
|
+
};
|
|
173
|
+
return output;
|
|
174
|
+
}
|
|
175
|
+
if (options.directory) {
|
|
176
|
+
const basename = path2.basename(filename);
|
|
177
|
+
let outputFilename = path2.join(options.directory, basename);
|
|
178
|
+
if (options.suffix) {
|
|
179
|
+
outputFilename = outputFilename.replace(/\.[^.]+$/, options.suffix);
|
|
180
|
+
}
|
|
181
|
+
return {
|
|
182
|
+
file: outputFilename,
|
|
183
|
+
filename: outputFilename
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
output = {
|
|
187
|
+
file: await open("/dev/stdout", "w"),
|
|
188
|
+
filename: "/dev/stdout"
|
|
189
|
+
};
|
|
190
|
+
return output;
|
|
191
|
+
}
|
|
192
|
+
async function main() {
|
|
193
|
+
const { values: options, positionals: filenames } = parseArgs(parseArgsConfig);
|
|
194
|
+
if (options.debug) {
|
|
195
|
+
console.debug(`${BIN}: debug mode enabled`);
|
|
196
|
+
}
|
|
197
|
+
if (options.help) {
|
|
198
|
+
help();
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
if (options.version) {
|
|
202
|
+
version();
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
if (filenames.length === 0) {
|
|
206
|
+
console.error(`${BIN}: no input files; use -- for stdin`);
|
|
207
|
+
help();
|
|
208
|
+
return -1;
|
|
209
|
+
}
|
|
210
|
+
await Promise.all(filenames.map(async (filename) => {
|
|
211
|
+
if (filename === "--") {
|
|
212
|
+
filename = "/dev/stdin";
|
|
213
|
+
}
|
|
214
|
+
options.debug && console.debug(`${BIN}: reading ${filename}...`);
|
|
215
|
+
const content = await readFile2(filename, { encoding: "utf-8" });
|
|
216
|
+
options.debug && console.debug(`${BIN}: templating ${filename}...`);
|
|
217
|
+
const output2 = await template(content, filename, options);
|
|
218
|
+
const { file: outputFile, filename: outputFilename } = await outputForFilename(filename, options);
|
|
219
|
+
options.debug && console.debug(`${BIN}: writing ${outputFilename}...`);
|
|
220
|
+
await writeFile(outputFile, output2, { encoding: "utf-8" });
|
|
221
|
+
}));
|
|
116
222
|
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
if (options.help) {
|
|
124
|
-
help();
|
|
125
|
-
return;
|
|
126
|
-
}
|
|
127
|
-
if (options.version) {
|
|
128
|
-
version();
|
|
129
|
-
return;
|
|
130
|
-
}
|
|
131
|
-
if (filenames.length === 0) {
|
|
132
|
-
console.error(`${index_1.BIN}: no input files; use -- for stdin`);
|
|
133
|
-
help();
|
|
134
|
-
return -1;
|
|
135
|
-
}
|
|
136
|
-
yield Promise.all(filenames.map((filename) => __awaiter(this, void 0, void 0, function* () {
|
|
137
|
-
if (filename === "--") {
|
|
138
|
-
filename = "/dev/stdin";
|
|
139
|
-
}
|
|
140
|
-
options.debug && console.debug(`${index_1.BIN}: reading ${filename}...`);
|
|
141
|
-
const content = yield fs_1.default.promises.readFile(filename, {
|
|
142
|
-
encoding: "utf-8",
|
|
143
|
-
});
|
|
144
|
-
options.debug && console.debug(`${index_1.BIN}: templating ${filename}...`);
|
|
145
|
-
const output = yield (0, index_1.template)(content, filename, options);
|
|
146
|
-
const { file: outputFile, filename: outputFilename } = yield outputForFilename(filename, options);
|
|
147
|
-
options.debug && console.debug(`${index_1.BIN}: writing ${outputFilename}...`);
|
|
148
|
-
yield fs_1.default.promises.writeFile(outputFile, output, {
|
|
149
|
-
encoding: "utf-8",
|
|
150
|
-
});
|
|
151
|
-
})));
|
|
152
|
-
});
|
|
223
|
+
try {
|
|
224
|
+
const exitCode = await main();
|
|
225
|
+
process2.exit(exitCode ?? 0);
|
|
226
|
+
} catch (e) {
|
|
227
|
+
console.error(`${BIN}: unhandled error:`, e);
|
|
228
|
+
process2.exit(-1);
|
|
153
229
|
}
|
|
154
|
-
main()
|
|
155
|
-
.then((i) => {
|
|
156
|
-
process_1.default.exit(i !== null && i !== void 0 ? i : 0);
|
|
157
|
-
})
|
|
158
|
-
.catch((e) => {
|
|
159
|
-
console.error(`${index_1.BIN}: unhandled error:`, e);
|
|
160
|
-
process_1.default.exit(-1);
|
|
161
|
-
});
|
package/dist/src/index.js
CHANGED
|
@@ -1,82 +1,92 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import { exec as execCallback } from "node:child_process";
|
|
3
|
+
import { readFile } from "node:fs/promises";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import process from "node:process";
|
|
6
|
+
import { promisify } from "node:util";
|
|
7
|
+
var exec = promisify(execCallback);
|
|
8
|
+
var BIN = "saladplate";
|
|
9
|
+
var cwdForFilename = (filename) => {
|
|
10
|
+
return filename === "/dev/stdin" ? process.cwd() : path.dirname(filename);
|
|
10
11
|
};
|
|
11
|
-
var
|
|
12
|
-
|
|
12
|
+
var VAR_RE = /\$\{\{([^\}]+)\}\}/g;
|
|
13
|
+
var FILE_RE = /\$\<\<([^\>]+)\>\>/g;
|
|
14
|
+
var EXEC_RE = /\$\(\(([^\)]+)\)\)/g;
|
|
15
|
+
var INJECT_RE = /\$\^\^([^\^]+)\^\^/;
|
|
16
|
+
async function replaceAsync(input, regexp, fn) {
|
|
17
|
+
const replacements = await Promise.all(Array.from(input.matchAll(regexp), (match) => fn(match)));
|
|
18
|
+
let i = 0;
|
|
19
|
+
return input.replace(regexp, () => replacements[i++]);
|
|
20
|
+
}
|
|
21
|
+
var replaceVar = (variable, filename, options) => {
|
|
22
|
+
options.debug && console.debug(`${BIN}: ${filename}: evaluating ${variable} for replacement...`);
|
|
23
|
+
return process.env[variable] ?? "";
|
|
13
24
|
};
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
const util_1 = __importDefault(require("util"));
|
|
21
|
-
/**
|
|
22
|
-
* The name of the tool; prefixes most output.
|
|
23
|
-
*/
|
|
24
|
-
exports.BIN = "saladplate";
|
|
25
|
-
const cwdForFilename = (filename) => {
|
|
26
|
-
return filename === "/dev/stdin" ? process_1.default.cwd() : path_1.default.dirname(filename);
|
|
25
|
+
var replaceFile = async (includeFilename, filename, options) => {
|
|
26
|
+
const cwd = cwdForFilename(filename);
|
|
27
|
+
const include = path.join(cwd, includeFilename);
|
|
28
|
+
options.debug && console.debug(`${BIN}: ${filename}: reading ${include} for replacement...`);
|
|
29
|
+
const content = await readFile(include, { encoding: "utf-8" });
|
|
30
|
+
return template(content, include, options);
|
|
27
31
|
};
|
|
28
|
-
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
const replacements = yield Promise.all(Array.from(input.matchAll(regexp), (match) => fn(match)));
|
|
34
|
-
let i = 0;
|
|
35
|
-
return input.replace(regexp, () => replacements[i++]);
|
|
36
|
-
});
|
|
37
|
-
}
|
|
38
|
-
const replaceVar = (variable, filename, options) => {
|
|
39
|
-
var _a;
|
|
40
|
-
options.debug && console.debug(`${exports.BIN}: ${filename}: evaluating ${variable} for replacement...`);
|
|
41
|
-
return (_a = process_1.default.env[variable]) !== null && _a !== void 0 ? _a : "";
|
|
32
|
+
var replaceExec = async (command, filename, options) => {
|
|
33
|
+
const cwd = cwdForFilename(filename);
|
|
34
|
+
options.debug && console.debug(`${BIN}: ${filename}: executing ${command} for replacement...`);
|
|
35
|
+
const { stdout } = await exec(command, { cwd });
|
|
36
|
+
return stdout;
|
|
42
37
|
};
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
const cwd = cwdForFilename(filename);
|
|
47
|
-
const include = path_1.default.join(cwd, includeFilename);
|
|
48
|
-
options.debug && console.debug(`${exports.BIN}: ${filename}: reading ${include} for replacement...`);
|
|
49
|
-
const content = yield fs_1.default.promises.readFile(include, { encoding: "utf-8" });
|
|
50
|
-
return (0, exports.template)(content, include, options);
|
|
51
|
-
});
|
|
52
|
-
const replaceExec = (command, filename, options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
53
|
-
// Execute the command in the working directory containing the file we are processing; if
|
|
54
|
-
// the current file is stdin then keep the current working directory.
|
|
55
|
-
const cwd = cwdForFilename(filename);
|
|
56
|
-
options.debug && console.debug(`${exports.BIN}: ${filename}: executing ${command} for replacement...`);
|
|
57
|
-
const { stdout } = yield util_1.default.promisify(child_process_1.exec)(command, {
|
|
58
|
-
cwd,
|
|
59
|
-
});
|
|
60
|
-
return stdout;
|
|
61
|
-
});
|
|
62
|
-
/**
|
|
63
|
-
* Replace variables, file includes, and command executions in the given `content`, which
|
|
64
|
-
* is assumed to have been read from the given `filename` (or stdin). Collapses newlines
|
|
65
|
-
* at the end of the output so that there's just one.
|
|
66
|
-
*/
|
|
67
|
-
const template = (content, filename, options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
68
|
-
filename !== null && filename !== void 0 ? filename : (filename = "/dev/stdin");
|
|
69
|
-
options !== null && options !== void 0 ? options : (options = {});
|
|
70
|
-
content = yield replaceAsync(content, VAR_RE, (match) => {
|
|
71
|
-
return replaceVar(match[1].trim(), filename, options);
|
|
72
|
-
});
|
|
73
|
-
content = yield replaceAsync(content, FILE_RE, (match) => {
|
|
74
|
-
return replaceFile(match[1].trim(), filename, options);
|
|
75
|
-
});
|
|
76
|
-
content = yield replaceAsync(content, EXEC_RE, (match) => {
|
|
77
|
-
return replaceExec(match[1].trim(), filename, options);
|
|
78
|
-
});
|
|
79
|
-
content = content.replace(/\n+$/m, "\n");
|
|
38
|
+
var processInjections = async (content, filename, options) => {
|
|
39
|
+
const match = INJECT_RE.exec(content);
|
|
40
|
+
if (!match) {
|
|
80
41
|
return content;
|
|
81
|
-
}
|
|
82
|
-
|
|
42
|
+
}
|
|
43
|
+
const injectFilename = match[1].trim();
|
|
44
|
+
let contentBefore = content.slice(0, match.index);
|
|
45
|
+
const rest = content.slice(match.index + match[0].length);
|
|
46
|
+
const innerResult = await processInjections(rest, filename, options);
|
|
47
|
+
const cwd = cwdForFilename(filename);
|
|
48
|
+
const inject = path.join(cwd, injectFilename);
|
|
49
|
+
options.debug && console.debug(`${BIN}: ${filename}: reading ${inject} for injection...`);
|
|
50
|
+
const wrapperContent = await readFile(inject, { encoding: "utf-8" });
|
|
51
|
+
let pattern = "^^";
|
|
52
|
+
if (innerResult.startsWith(`
|
|
53
|
+
`) && wrapperContent.includes(`
|
|
54
|
+
^^`)) {
|
|
55
|
+
pattern = `
|
|
56
|
+
^^`;
|
|
57
|
+
}
|
|
58
|
+
if (innerResult.endsWith(`
|
|
59
|
+
`) && wrapperContent.includes(`^^
|
|
60
|
+
`)) {
|
|
61
|
+
pattern = pattern + `
|
|
62
|
+
`;
|
|
63
|
+
}
|
|
64
|
+
const wrapped = wrapperContent.replace(pattern, innerResult);
|
|
65
|
+
if (contentBefore.endsWith(`
|
|
66
|
+
`) && innerResult.startsWith(`
|
|
67
|
+
`)) {
|
|
68
|
+
contentBefore = contentBefore.slice(0, -1);
|
|
69
|
+
}
|
|
70
|
+
return contentBefore + wrapped;
|
|
71
|
+
};
|
|
72
|
+
var template = async (content, filename, options) => {
|
|
73
|
+
filename ??= "/dev/stdin";
|
|
74
|
+
options ??= {};
|
|
75
|
+
content = await replaceAsync(content, VAR_RE, (match) => {
|
|
76
|
+
return replaceVar(match[1].trim(), filename, options);
|
|
77
|
+
});
|
|
78
|
+
content = await replaceAsync(content, FILE_RE, (match) => {
|
|
79
|
+
return replaceFile(match[1].trim(), filename, options);
|
|
80
|
+
});
|
|
81
|
+
content = await replaceAsync(content, EXEC_RE, (match) => {
|
|
82
|
+
return replaceExec(match[1].trim(), filename, options);
|
|
83
|
+
});
|
|
84
|
+
content = await processInjections(content, filename, options);
|
|
85
|
+
content = content.replace(/\n+$/m, `
|
|
86
|
+
`);
|
|
87
|
+
return content;
|
|
88
|
+
};
|
|
89
|
+
export {
|
|
90
|
+
template,
|
|
91
|
+
BIN
|
|
92
|
+
};
|
package/package.json
CHANGED
|
@@ -1,24 +1,27 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "saladplate",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"type": "module",
|
|
4
5
|
"description": "Very simple templating.",
|
|
5
6
|
"keywords": [
|
|
6
7
|
"template",
|
|
7
8
|
"templating"
|
|
8
9
|
],
|
|
9
|
-
"main": "./dist/src/
|
|
10
|
+
"main": "./dist/src/index.js",
|
|
11
|
+
"exports": {
|
|
12
|
+
".": "./dist/src/index.js"
|
|
13
|
+
},
|
|
10
14
|
"bin": {
|
|
11
15
|
"saladplate": "dist/bin/saladplate.js"
|
|
12
16
|
},
|
|
13
17
|
"scripts": {
|
|
14
18
|
"types": "tsc --noEmit",
|
|
15
|
-
"build": "./build.sh",
|
|
16
|
-
"saladplate": "
|
|
19
|
+
"build": "bun run types && ./build.sh",
|
|
20
|
+
"saladplate": "bun ./bin/saladplate.ts",
|
|
17
21
|
"test": "./test.sh"
|
|
18
22
|
},
|
|
19
23
|
"devDependencies": {
|
|
20
24
|
"@types/node": "^20.12.12",
|
|
21
|
-
"ts-node": "^10.9.2",
|
|
22
25
|
"typescript": "^5.4.5"
|
|
23
26
|
},
|
|
24
27
|
"files": [
|
package/dist/bin/saladplate.d.ts
DELETED
package/dist/src/index.d.ts
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The name of the tool; prefixes most output.
|
|
3
|
-
*/
|
|
4
|
-
export declare const BIN = "saladplate";
|
|
5
|
-
/**
|
|
6
|
-
* Templating options for `template`.
|
|
7
|
-
*/
|
|
8
|
-
export type Options = {
|
|
9
|
-
debug?: boolean;
|
|
10
|
-
};
|
|
11
|
-
/**
|
|
12
|
-
* Replace variables, file includes, and command executions in the given `content`, which
|
|
13
|
-
* is assumed to have been read from the given `filename` (or stdin). Collapses newlines
|
|
14
|
-
* at the end of the output so that there's just one.
|
|
15
|
-
*/
|
|
16
|
-
export declare const template: (content: string, filename?: string, options?: Options) => Promise<string>;
|