UNPKG

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