Skip to content

Commit e323add

Browse files
authored
Merge pull request #47 from gsandf/feature/add-rendering-folders
✨ add `renderGlob` and `renderToFolder`
2 parents 6e00273 + 03a0066 commit e323add

File tree

11 files changed

+159
-25
lines changed

11 files changed

+159
-25
lines changed

.eslintignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
dist/*
2+
__tests/helpers/output/

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ node_modules
33

44
# Built code
55
dist/
6+
__tests__/helpers/output/
7+
__tests__/helpers/large/
68

79
# Logs
810
logs
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{ "fullName": "{{ name }}" }
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Hello, {{name}}!
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
This {{ name }} is not changed.

__tests__/index.ts

Lines changed: 96 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
11
import test from 'ava';
22
import { promises as fs } from 'fs';
3+
import mkdirp from 'mkdirp';
34
import path from 'path';
4-
import { renderString, renderTemplateFile } from '../src';
5+
import {
6+
renderGlob,
7+
renderString,
8+
renderTemplateFile,
9+
renderToFolder
10+
} from '../src';
11+
import { limitOpenFiles } from '../src/utils';
512

613
test('Data is replaced when given string', t => {
714
// Should return the same without regard of consistent spacing
@@ -67,3 +74,91 @@ test('Data is replaced when given file path', async t => {
6774

6875
t.is(actual, expected);
6976
});
77+
78+
test('Renders from a glob', async t => {
79+
const actualFiles: { name: string; contents: string }[] = [];
80+
const expectedFiles = [
81+
{
82+
name: './__tests__/helpers/templates/also-cool.json',
83+
contents: '{ "fullName": "Bob" }\n'
84+
},
85+
{
86+
name: './__tests__/helpers/templates/cool.md',
87+
contents: '# Hello, Bob!\n'
88+
}
89+
];
90+
91+
await renderGlob(
92+
'./__tests__/helpers/templates/**/*.!(txt)',
93+
{ name: 'Bob' },
94+
(name, contents) => {
95+
actualFiles.push({ name, contents });
96+
}
97+
);
98+
99+
t.is(actualFiles.length, 2);
100+
101+
if (actualFiles[0].name === expectedFiles[0].name) {
102+
t.deepEqual(actualFiles, expectedFiles);
103+
} else {
104+
t.deepEqual(actualFiles.reverse(), expectedFiles);
105+
}
106+
});
107+
108+
test('Can render output to a file', async t => {
109+
const expectedFiles = [
110+
{
111+
name: './__tests__/helpers/output/also-cool.json',
112+
contents: '{ "fullName": "Kai" }\n'
113+
},
114+
{
115+
name: './__tests__/helpers/output/cool.md',
116+
contents: '# Hello, Kai!\n'
117+
}
118+
];
119+
120+
await renderToFolder(
121+
'./__tests__/helpers/templates/**/*.!(txt)',
122+
'./__tests__/helpers/output',
123+
{ name: 'Kai' }
124+
);
125+
126+
for (const { name, contents } of expectedFiles) {
127+
const actualContents = await fs.readFile(name, { encoding: 'utf-8' });
128+
t.is(actualContents, contents);
129+
}
130+
});
131+
132+
test('Can render a ton of files', async t => {
133+
const expectedFiles = [] as { name: string; contents: string }[];
134+
135+
// Pre-test setup
136+
const templateFolder = './__tests__/helpers/large/';
137+
const outputFolder = `${templateFolder}/output`;
138+
const template = 'Hello, {{ name }}';
139+
140+
await mkdirp(templateFolder);
141+
await Promise.all(
142+
Array.from({ length: 50000 }, (_, i) => {
143+
const basename = `${i}.template`;
144+
145+
expectedFiles.push({
146+
name: `${outputFolder}/${basename}`,
147+
contents: 'Hello, Test'
148+
});
149+
150+
return limitOpenFiles(() =>
151+
fs.writeFile(`${templateFolder}/${basename}`, template)
152+
);
153+
})
154+
);
155+
156+
await renderToFolder(`${templateFolder}/*.template`, outputFolder, {
157+
name: 'Test'
158+
});
159+
160+
for (const { name, contents } of expectedFiles) {
161+
const actualContents = await fs.readFile(name, { encoding: 'utf-8' });
162+
t.is(actualContents, contents);
163+
}
164+
});

package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@
3939
],
4040
"require": [
4141
"ts-node/register"
42-
]
42+
],
43+
"timeout": "30s"
4344
},
4445
"browserslist": [
4546
">0.2%",
@@ -50,6 +51,7 @@
5051
"@blakek/deep": "^2.1.1",
5152
"glob": "^7.1.6",
5253
"meow": "^8.0.0",
54+
"mkdirp": "^1.0.4",
5355
"p-limit": "^3.1.0"
5456
},
5557
"devDependencies": {
@@ -63,6 +65,7 @@
6365
"@rollup/plugin-node-resolve": "^11.0.0",
6466
"@rollup/plugin-typescript": "^8.0.0",
6567
"@types/glob": "^7.1.3",
68+
"@types/mkdirp": "^1.0.1",
6669
"@typescript-eslint/eslint-plugin": "^4.9.0",
6770
"@typescript-eslint/parser": "^4.9.0",
6871
"amper-scripts": "^1.0.0-1",

src/cli.ts

Lines changed: 2 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,10 @@
11
#!/usr/bin/env node
22

3-
import { promises as fs } from 'fs';
4-
import _glob from 'glob';
53
import meow from 'meow';
6-
import pLimit from 'p-limit';
74
import path from 'path';
8-
import { promisify } from 'util';
9-
import { renderTemplateFile } from '.';
5+
import { renderToFolder } from '.';
106

117
async function main() {
12-
const glob = promisify(_glob);
13-
const limitOpenFiles = pLimit(64);
14-
158
const cli = meow(`
169
Usage
1710
$ template-file <dataFile> <sourceGlob> <destination>
@@ -36,21 +29,7 @@ async function main() {
3629
const [dataFile, sourceGlob, destination] = cli.input;
3730
const data = await import(path.resolve(dataFile));
3831

39-
function renderToFile(file: string, destination: string) {
40-
return limitOpenFiles(() =>
41-
renderTemplateFile(file, data).then(renderedString =>
42-
fs.writeFile(destination, renderedString)
43-
)
44-
);
45-
}
46-
47-
glob(sourceGlob)
48-
.then(files =>
49-
files.map(file =>
50-
renderToFile(file, path.join(destination, path.basename(file)))
51-
)
52-
)
53-
.then(fileWriteOperations => Promise.all(fileWriteOperations));
32+
renderToFolder(sourceGlob, destination, data);
5433
}
5534

5635
main();

src/index.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,31 @@
11
import { get } from '@blakek/deep';
22
import { promises as fs } from 'fs';
3+
import _glob from 'glob';
4+
import mkdirp from 'mkdirp';
5+
import path from 'path';
6+
import { promisify } from 'util';
7+
import { limitOpenFiles } from './utils';
38

49
interface Data
510
extends Record<
611
string | number | symbol,
712
string | number | Data | (() => string | number | Data)
813
> {}
914

15+
export async function renderGlob(
16+
sourceGlob: string,
17+
data: Data,
18+
onFileCallback: (filename: string, contents: string) => void
19+
): Promise<void> {
20+
const glob = promisify(_glob);
21+
const files = await glob(sourceGlob);
22+
23+
for (const file of files) {
24+
const contents = await limitOpenFiles(() => renderTemplateFile(file, data));
25+
onFileCallback(file, contents);
26+
}
27+
}
28+
1029
export function renderString(
1130
template: string,
1231
data: Data
@@ -35,3 +54,18 @@ export async function renderTemplateFile(
3554
const templateString = await fs.readFile(filepath, { encoding: 'utf-8' });
3655
return renderString(templateString, data);
3756
}
57+
58+
export async function renderToFolder(
59+
sourceGlob: string,
60+
destination: string,
61+
data: Data
62+
): Promise<void> {
63+
await mkdirp(destination);
64+
65+
function writeFile(filename: string, contents: string) {
66+
const fullPath = path.join(destination, path.basename(filename));
67+
fs.writeFile(fullPath, contents);
68+
}
69+
70+
return renderGlob(sourceGlob, data, writeFile);
71+
}

src/utils.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import pLimit from 'p-limit';
2+
3+
const OPEN_FILE_LIMIT = Number(process.env.TF_FILE_LIMIT) || 1024;
4+
5+
export const limitOpenFiles = pLimit(OPEN_FILE_LIMIT);

0 commit comments

Comments
 (0)