UNPKG

5.99 kBJavaScriptView Raw
1const Metalsmith = require("metalsmith");
2const Handlebars = require("handlebars");
3const async = require("async");
4const render = require("consolidate").handlebars.render;
5const path = require("path");
6const multimatch = require("multimatch");
7const getOptions = require("./options");
8const ask = require("./ask");
9const filter = require("./filter");
10
11// register handlebars helper
12Handlebars.registerHelper("if_eq", function(a, b, opts) {
13 return a === b ?
14 opts.fn(this) :
15 opts.inverse(this);
16});
17
18Handlebars.registerHelper("unless_eq", function(a, b, opts) {
19 return a === b ?
20 opts.inverse(this) :
21 opts.fn(this);
22});
23
24/**
25 * Generate a template given a `src` and `dest`.
26 *
27 * @param {String} name
28 * @param {String} src
29 * @param {String} dest
30 * @param {Function} done
31 */
32
33module.exports = function generate(name, src, dest, done) {
34 const opts = getOptions(name, src);
35 const metalsmith = Metalsmith(path.join(src, "template"));
36 const data = Object.assign(metalsmith.metadata(), {
37 destDirName: name,
38
39 inPlace: dest === process.cwd(),
40 noEscape: true,
41 });
42 opts.helpers && Object.keys(opts.helpers).map((key) => {
43 Handlebars.registerHelper(key, opts.helpers[key]);
44 });
45 metalsmith
46 .metadata({
47 createDate: DateFormater.get("yyyy-mm-dd"),
48 year: DateFormater.get("yyyy"),
49 month: DateFormater.get("mm"),
50 })
51 .use(askQuestions(opts.prompts))
52 .use(filterFiles(opts.filters))
53 .use(renderTemplateFiles(opts.skipInterpolation))
54 .clean(false)
55 .source(".") // start from template root instead of `./src` which is Metalsmith's default for `source`
56 .destination(dest)
57 .build((err) => {
58 done(err);
59 logMessage(opts.completeMessage, data);
60 });
61
62 return data;
63};
64
65/**
66 * Create a middleware for asking questions.
67 *
68 * @param {Object} prompts
69 * @return {Function}
70 */
71
72function askQuestions(prompts) {
73 return function(files, metalsmith, done) {
74 ask(prompts, metalsmith.metadata(), done);
75 };
76}
77
78/**
79 * Create a middleware for filtering files.
80 *
81 * @param {Object} filters
82 * @return {Function}
83 */
84
85function filterFiles(filters) {
86 return function(files, metalsmith, done) {
87 filter(files, filters, metalsmith.metadata(), done);
88 };
89}
90
91/**
92 * Template in place plugin.
93 *
94 * @param {Object} files
95 * @param {Metalsmith} metalsmith
96 * @param {Function} done
97 */
98
99function renderTemplateFiles(skipInterpolation) {
100 skipInterpolation = typeof skipInterpolation === "string" ? [skipInterpolation] :
101 skipInterpolation;
102 return function(files, metalsmith, done) {
103 const keys = Object.keys(files);
104 const metalsmithMetadata = metalsmith.metadata();
105 async.each(keys, (file, next) => {
106 // skipping files with skipInterpolation option
107 if (skipInterpolation && multimatch([file], skipInterpolation, {
108 dot: true
109 }).length) {
110 return next();
111 }
112 const str = files[file].contents.toString();
113 // do not attempt to render files that do not have mustaches
114 if (!/{{([^{}]+)}}/g.test(str)) {
115 return next();
116 }
117 render(str, metalsmithMetadata, (err, res) => {
118 if (err) return next(err);
119 files[file].contents = new Buffer(res);
120 next();
121 });
122 }, done);
123 };
124}
125
126/**
127 * Display template complete message.
128 *
129 * @param {String} message
130 * @param {Object} data
131 */
132
133function logMessage(message, data) {
134 if (!message) return;
135 render(message, data, (err, res) => {
136 if (err) {
137 console.error(`\n Error when rendering template complete message: ${err.message.trim()}`);
138 } else {
139 console.log(`\n${res.split(/\r?\n/g).map(line => ` ${line}`).join("\n")}`);
140 }
141 });
142}
143
144
145var DateFormater = (function () {
146 // Private methods
147 const text = [];
148 const repAllText = function (t) {
149 const treg = t.match(/('[^']*')|("[^"]*")/ig);
150 if (treg) {
151 for (let i in treg) {
152 t = t.replace(treg[i], `\${${i}}`);
153 text.push(treg[i]);
154 }
155 } else {
156 for (let i in text) {
157 t = t.replace(`\${${i}}`, text[i].replace(/'|"/g, ""));
158 }
159 }
160 return t;
161 };
162 const getDate = function (who, date) {
163 let check = who.toLowerCase(),
164 add = check.match(/[\+-]\d+/),
165 result = "";
166 if (add) add = parseInt(add, 10);
167 else add = 0;
168 switch (check.replace(/[\+-]\d+/, "")) {
169 case "yyyy":
170 date.setFullYear(date.getFullYear() + add);
171 result = date.getFullYear();
172 break;
173 case "yy":
174 result = date.getFullYear().toString().substring(2) + add;
175 break;
176 case "mm":
177 date.setMonth(date.getMonth() + add);
178 result = dn(date.getMonth() + 1);
179 break;
180 case "dd":
181 date.setDate(date.getDate() + add);
182 result = dn(date.getDate());
183 break;
184 case "h":
185 date.setHours(date.getHours() + add);
186 result = dn(date.getHours());
187 break;
188 case "m":
189 date.setMinutes(date.getMinutes() + add);
190 result = dn(date.getMinutes());
191 break;
192 case "s":
193 result = dn(date.getSeconds());
194 break;
195 case "ms":
196 result = tn(date.getMilliseconds());
197 break;
198 }
199 return result;
200 };
201 var dn = function (n) {
202 if (n < 10) return `0${n}`;
203 return n;
204 };
205 var tn = function (n) {
206 if (n < 100 && n >= 10) return `0${n}`;
207 else if (n < 10) return `00${n}`;
208 return n;
209 };
210 // Public methods
211 return {
212 get(f) {
213 const reg = /(y{4}|y{2}|m{2}|d{2}|H|ms|M|S)([\+-]\d+)?|(?:\'[^\']*\')/g;
214 let vals = f.match(reg),
215 now = new Date();
216 f = repAllText(f);
217 if (vals) {
218 for (const step1 in vals) {
219 if (/[a-zA-Z][\+-]\d+/.test(vals[step1])) getDate(vals[step1], now);
220 }
221 f = f.replace(/(y{4}|y{2}|m{2}|d{2}|H|ms|M|S)[\+-]\d+/ig, "$1");
222 vals = f.match(reg);
223 for (const step2 in vals) f = f.replace(vals[step2], `${getDate(vals[step2], now)}`);
224 }
225 f = repAllText(f);
226 return f;
227 },
228 };
229}());
\No newline at end of file