-
Notifications
You must be signed in to change notification settings - Fork 0
/
cli.js
executable file
·185 lines (149 loc) · 4.64 KB
/
cli.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
#! /usr/bin/env node
'use strict';
const p = require('path');
const fs = require('fs-extra');
const program = require('commander');
const ejs = require('ejs');
const exists = require('file-exists');
const pkg = require('./package.json');
const log = require('./lib/log.js');
const inputExt = '.ejs';
const outputExt = '.html';
let options = {};
program
.version(pkg.version)
.arguments('<config> [dest]')
.option('-r, --read <variable_name>', 'Read contents from stdin, if available, and pipe to a given global variable name in the config.')
.action((config, dest) => {
options.config = config;
options.dest = dest;
});
program.parse(process.argv);
if (!options.config) {
return log.error('A path to a config file must be declared.');
}
if (!exists(options.config)) {
return log.error(`Config path does not exist: ${options.config}`);
}
const config = require(p.resolve(process.cwd(), options.config));
if (!config.files || !config.files.length) {
return log.error(`No files have been declared in ${options.config}`)
}
if (!!program.read) {
process.stdin
.setEncoding('utf8')
.on('readable', () => {
let chunk = process.stdin.read();
config.globals[program.read] = config.globals[program.read] || '';
if (chunk !== null) {
config.globals[program.read] = [config.globals[program.read], chunk].join('');
} else {
run();
process.stdin.emit('end');
}
});
} else {
run();
}
/////////////////
/**
* Transforms the declared "include" paths in the template
* to make sure they are relative to the entry layout.
*
* @param {String} data The raw template string
* @param {Object} fileConfig A config object that includes a template, path, and locals
* @return {String} The transformed template string
*/
function fixIncludePaths(data, fileConfig) {
const pattern = /include\s?\(['"]([\w/]+)['"]/g;
if (!data.match(pattern)) {
return data;
}
return data.replace(pattern, (str, p1, offset, s) => {
return `include('${p.join(p.dirname(options.config), p1)}'`;
});
}
/**
* Retrieves the data that should be passed into the ejs template
* @param {Object} fileConfig A config object that includes a template, path, and locals
*/
function getData(fileConfig) {
fileConfig.globals = fileConfig.globals || {};
fileConfig.locals = fileConfig.locals || {};
return Object.assign({}, config.globals, fileConfig.locals);
}
/**
* Returns a filename ensuring that the extnsion is included
* @param {String} filename A filename
* @param {String} ext The desired extension
* @return {String} The new filename
*/
function ensureExt(filename, ext) {
if (p.extname(filename) !== ext) {
return [filename, ext].join('');
} else {
return filename;
}
}
/**
* Handles what to do with the rendered HTML.
* @param {Object} fileConfig A config object that includes a template, dest, and locals
* @param {String} template The template data
*/
function handleOut(fileConfig, template) {
const ejsSettings = {
filename: "."
};
let rendered = ejs.render(template, getData(fileConfig), ejsSettings);
if (!fileConfig.dest) {
return process.stdout.write(rendered);
}
options.dest = options.dest || '.';
let dest = p.resolve(options.dest, ensureExt(fileConfig.dest, outputExt));
fs.ensureDir(p.dirname(dest), (err) => {
if (err) {
return log.error(err);
}
fs.writeFile(dest, rendered, (err) => {
if (err) {
return log.error(err);
}
});
});
}
/**
* Renders an EJS layout from a given config object
* @param {Object} fileConfig A config object that includes a template, dest, and locals
*/
function render(fileConfig) {
if (!validateFileConfig(fileConfig)) {
return;
}
let templatePath = p.resolve(p.dirname(options.config), ensureExt(fileConfig.template, inputExt));
fs.readFile(templatePath, 'utf8', (err, template) => {
if (err) {
return log.error(err);
}
handleOut(fileConfig, fixIncludePaths(template, fileConfig));
});
}
function run() {
config.files.forEach(render);
}
/**
* Validates that a given config object for a file is valid
* @param {Object} fileConfig A config object that includes a template, path, and locals
* @return {Boolean} Whether or not the config is valid
*/
function validateFileConfig(fileConfig) {
const requiredParams = ['template'];
let i = 0;
let len = requiredParams.length;
for (i; i < len; i++) {
if (!fileConfig[requiredParams[i]]) {
log.error(`"${requiredParams[i]}" must be present in order to create file. \n${JSON.stringify(fileConfig, null, 2)}`);
return false;
}
}
return true;
}