1 | | // Generated by CoffeeScript 1.4.0 |
2 | 1 | var conditions, each, eco, exec, fs, mecano, misc, open, path, rimraf, util, _ref; |
3 | | |
4 | 1 | fs = require('fs'); |
5 | | |
6 | 1 | path = require('path'); |
7 | | |
8 | 1 | if ((_ref = fs.exists) == null) { |
9 | 0 | fs.exists = path.exists; |
10 | | } |
11 | | |
12 | 1 | util = require('util'); |
13 | | |
14 | 1 | each = require('each'); |
15 | | |
16 | 1 | eco = require('eco'); |
17 | | |
18 | 1 | rimraf = require('rimraf'); |
19 | | |
20 | 1 | exec = require('child_process').exec; |
21 | | |
22 | 1 | open = require('open-uri'); |
23 | | |
24 | 1 | conditions = require('./conditions'); |
25 | | |
26 | 1 | misc = require('./misc'); |
27 | | |
28 | | /* |
29 | | |
30 | | Mecano gather a set of functions usually used during system deployment. All the functions share a |
31 | | common API with flexible options. |
32 | | */ |
33 | | |
34 | | |
35 | 1 | mecano = module.exports = { |
36 | | /* |
37 | | |
38 | | `cp` `copy(options, callback)` |
39 | | ------------------------------ |
40 | | |
41 | | Copy a file. |
42 | | |
43 | | `options` Command options include: |
44 | | |
45 | | * `source` The file or directory to copy. |
46 | | * `destination` Where the file or directory is copied. |
47 | | * `force` Copy the file even if one already exists. |
48 | | * `not_if_exists` Equals destination if true. |
49 | | * `chmod` Permissions of the file or the parent directory |
50 | | |
51 | | `callback` Received parameters are: |
52 | | |
53 | | * `err` Error object if any. |
54 | | * `copied` Number of files or parent directories copied. |
55 | | |
56 | | todo: |
57 | | * deal with directories |
58 | | * preserve permissions if `chmod` is `true` |
59 | | * Compare files with checksum |
60 | | */ |
61 | | |
62 | | copy: function(options, callback) { |
63 | 7 | var copied; |
64 | 7 | options = misc.options(options); |
65 | 7 | copied = 0; |
66 | 7 | return each(options).on('item', function(next, options) { |
67 | 7 | var chmod, copy, dstStat, finish, source; |
68 | 7 | if (!options.source) { |
69 | 0 | return next(new Error('Missing source')); |
70 | | } |
71 | 7 | if (!options.destination) { |
72 | 0 | return next(new Error('Missing destination')); |
73 | | } |
74 | 7 | if (options.not_if_exists === true) { |
75 | 0 | options.not_if_exists = options.destination; |
76 | | } |
77 | 7 | dstStat = null; |
78 | 7 | source = function() { |
79 | 0 | return fs.stat(options.source, function(err, stat) { |
80 | 0 | if (err) { |
81 | 0 | return next(err); |
82 | | } |
83 | 0 | if (stat.isDirectory()) { |
84 | 0 | return next(new Error('Source is a directory')); |
85 | | } |
86 | 0 | return copy(); |
87 | | }); |
88 | | }; |
89 | 7 | copy = function(destination) { |
90 | 9 | if (destination == null) { |
91 | 7 | destination = options.destination; |
92 | | } |
93 | 9 | return fs.stat(destination, function(err, stat) { |
94 | 9 | var dirExists, fileExists, input, output; |
95 | 9 | dstStat = stat; |
96 | 9 | if (err && err.code !== 'ENOENT') { |
97 | 0 | return next(err); |
98 | | } |
99 | 9 | dirExists = !err && stat.isDirectory(); |
100 | 9 | fileExists = !err && stat.isFile(); |
101 | 9 | if (fileExists && !options.force) { |
102 | 2 | return next(null, 0); |
103 | | } |
104 | 7 | if (dirExists) { |
105 | 2 | return copy(path.resolve(options.destination, path.basename(options.source))); |
106 | | } |
107 | 5 | input = fs.createReadStream(options.source); |
108 | 5 | output = fs.createWriteStream(destination); |
109 | 5 | return util.pump(input, output, function(err) { |
110 | 5 | if (err) { |
111 | 0 | return next(err); |
112 | | } |
113 | 5 | return chmod(); |
114 | | }); |
115 | | }); |
116 | | }; |
117 | 7 | chmod = function() { |
118 | 5 | if (!options.chmod || options.chmod === dstStat.mode) { |
119 | 5 | return finish(); |
120 | | } |
121 | 0 | return fs.chmod(options.destination, options.chmod, function(err) { |
122 | 0 | if (err) { |
123 | 0 | return next(err); |
124 | | } |
125 | 0 | return finish(); |
126 | | }); |
127 | | }; |
128 | 7 | finish = function() { |
129 | 5 | copied++; |
130 | 5 | return next(); |
131 | | }; |
132 | 7 | return conditions.all(options, next, copy); |
133 | | }).on('both', function(err) { |
134 | 7 | return callback(err, copied); |
135 | | }); |
136 | | }, |
137 | | /* |
138 | | |
139 | | `download(options, callback)` |
140 | | ----------------------------- |
141 | | |
142 | | Download files using various protocols. The excellent |
143 | | [open-uri](https://github.com/publicclass/open-uri) module provides support for HTTP(S), |
144 | | file and FTP. All the options supported by open-uri are passed to it. |
145 | | |
146 | | Note, GIT is not yet supported but documented as a wished feature. |
147 | | |
148 | | `options` Command options include: |
149 | | |
150 | | * `source` File, HTTP URL, FTP, GIT repository. File is the default protocol if source is provided without a scheme. |
151 | | * `destination` Path where the file is downloaded. |
152 | | * `force` Overwrite destination file if it exists. |
153 | | |
154 | | `callback` Received parameters are: |
155 | | |
156 | | * `err` Error object if any. |
157 | | * `downloaded` Number of downloaded files |
158 | | |
159 | | Basic example: |
160 | | mecano.download |
161 | | source: 'https://github.com/wdavidw/node-sigar/tarball/v0.0.1' |
162 | | destination: 'node-sigar.tgz' |
163 | | , (err, downloaded) -> |
164 | | fs.exists 'node-sigar.tgz', (exists) -> |
165 | | assert.ok exists |
166 | | */ |
167 | | |
168 | | download: function(options, callback) { |
169 | 6 | var downloaded; |
170 | 6 | options = misc.options(options); |
171 | 6 | downloaded = 0; |
172 | 6 | return each(options).on('item', function(next, options) { |
173 | 6 | var download, _ref1; |
174 | 6 | if (!options.source) { |
175 | 0 | return next(new Error("Missing source: " + options.source)); |
176 | | } |
177 | 6 | if (!options.destination) { |
178 | 0 | return next(new Error("Missing destination: " + options.destination)); |
179 | | } |
180 | 6 | if ((_ref1 = options.force) == null) { |
181 | 6 | options.force = false; |
182 | | } |
183 | 6 | download = function() { |
184 | 3 | var destination; |
185 | 3 | destination = fs.createWriteStream(options.destination); |
186 | 3 | open(options.source, destination); |
187 | 3 | destination.on('close', function() { |
188 | 3 | downloaded++; |
189 | 3 | return next(); |
190 | | }); |
191 | 3 | return destination.on('error', function(err) { |
192 | 0 | return next(err); |
193 | | }); |
194 | | }; |
195 | 6 | return fs.exists(options.destination, function(exists) { |
196 | 6 | if (exists && !options.force) { |
197 | 3 | return next(); |
198 | 3 | } else if (exists) { |
199 | 0 | return rimraf(options.destination, function(err) { |
200 | 0 | if (err) { |
201 | 0 | return next(err); |
202 | | } |
203 | 0 | return download(); |
204 | | }); |
205 | | } else { |
206 | 3 | return download(); |
207 | | } |
208 | | }); |
209 | | }).on('both', function(err) { |
210 | 6 | return callback(err, downloaded); |
211 | | }); |
212 | | }, |
213 | | /* |
214 | | |
215 | | `exec` `execute([goptions], options, callback)` |
216 | | ----------------------------------------------- |
217 | | Run a command locally or with ssh if the `host` is provided. Global options is |
218 | | optional and is used in case where options is defined as an array of |
219 | | multiple commands. Note, `opts` inherites all the properties of `goptions`. |
220 | | |
221 | | `goptions` Global options includes: |
222 | | |
223 | | * `parallel` Wether the command are run in sequential, parallel |
224 | | or limited concurrent mode. See the `node-each` documentation for more |
225 | | details. Default to sequential (false). |
226 | | |
227 | | `options` Include all conditions as well as: |
228 | | |
229 | | * `cmd` String, Object or array; Command to execute. |
230 | | * `env` Environment variables, default to `process.env`. |
231 | | * `cwd` Current working directory. |
232 | | * `uid` Unix user id. |
233 | | * `gid` Unix group id. |
234 | | * `code` Expected code(s) returned by the command, int or array of int, default to 0. |
235 | | * `host` SSH host or IP address. |
236 | | * `username` SSH host or IP address. |
237 | | * `stdout` Writable EventEmitter in which command output will be piped. |
238 | | * `stderr` Writable EventEmitter in which command error will be piped. |
239 | | |
240 | | `callback` Received parameters are: |
241 | | |
242 | | * `err` Error if any. |
243 | | * `executed` Number of executed commandes. |
244 | | * `stdout` Stdout value(s) unless `stdout` option is provided. |
245 | | * `stderr` Stderr value(s) unless `stderr` option is provided. |
246 | | */ |
247 | | |
248 | | execute: function(goptions, options, callback) { |
249 | 18 | var escape, executed, isArray, stderrs, stdouts; |
250 | 18 | if (arguments.length === 2) { |
251 | 18 | callback = options; |
252 | 18 | options = goptions; |
253 | | } |
254 | 18 | isArray = Array.isArray(options); |
255 | 18 | options = misc.options(options); |
256 | 18 | executed = 0; |
257 | 18 | stdouts = []; |
258 | 18 | stderrs = []; |
259 | 18 | escape = function(cmd) { |
260 | 1 | var char, esccmd, _i, _len; |
261 | 1 | esccmd = ''; |
262 | 1 | for (_i = 0, _len = cmd.length; _i < _len; _i++) { |
263 | 20 | char = cmd[_i]; |
264 | 20 | if (char === '$') { |
265 | 1 | esccmd += '\\'; |
266 | | } |
267 | 20 | esccmd += char; |
268 | | } |
269 | 1 | return esccmd; |
270 | | }; |
271 | 18 | return each(options).parallel(goptions.parallel).on('item', function(next, option, i) { |
272 | 18 | var cmd, cmdOption, _ref1; |
273 | 18 | if (typeof option === 'string') { |
274 | 0 | option = { |
275 | | cmd: option |
276 | | }; |
277 | | } |
278 | 18 | misc.merge(true, option, goptions); |
279 | 18 | if (option.cmd == null) { |
280 | 0 | return next(new Error("Missing cmd: " + option.cmd)); |
281 | | } |
282 | 18 | if ((_ref1 = option.code) == null) { |
283 | 17 | option.code = [0]; |
284 | | } |
285 | 18 | if (!Array.isArray(option.code)) { |
286 | 0 | option.code = [option.code]; |
287 | | } |
288 | 18 | cmdOption = {}; |
289 | 18 | cmdOption.env = option.env || process.env; |
290 | 18 | cmdOption.cwd = option.cwd || null; |
291 | 18 | if (options.uid) { |
292 | 0 | cmdOption.uid = option.uid; |
293 | | } |
294 | 18 | if (options.gid) { |
295 | 0 | cmdOption.gid = option.gid; |
296 | | } |
297 | 18 | cmd = function() { |
298 | 17 | var run, stderr, stdout; |
299 | 17 | if (option.host) { |
300 | 1 | option.cmd = escape(option.cmd); |
301 | 1 | option.cmd = option.host + ' "' + option.cmd + '"'; |
302 | 1 | if (option.username) { |
303 | 0 | option.cmd = option.username + '@' + option.cmd; |
304 | | } |
305 | 1 | option.cmd = 'ssh -o StrictHostKeyChecking=no ' + option.cmd; |
306 | | } |
307 | 17 | run = exec(option.cmd, cmdOption); |
308 | 17 | stdout = stderr = ''; |
309 | 17 | if (option.stdout) { |
310 | 1 | run.stdout.pipe(option.stdout); |
311 | | } else { |
312 | 16 | run.stdout.on('data', function(data) { |
313 | 13 | return stdout += data; |
314 | | }); |
315 | | } |
316 | 17 | if (option.stderr) { |
317 | 0 | run.stderr.pipe(option.stderr); |
318 | | } else { |
319 | 17 | run.stderr.on('data', function(data) { |
320 | 5 | return stderr += data; |
321 | | }); |
322 | | } |
323 | 17 | return run.on("exit", function(code) { |
324 | 17 | return setTimeout(function() { |
325 | 17 | var err; |
326 | 17 | executed++; |
327 | 17 | stdouts.push(option.stdout ? void 0 : stdout); |
328 | 17 | stderrs.push(option.stderr ? void 0 : stderr); |
329 | 17 | if (option.code.indexOf(code) === -1) { |
330 | 1 | err = new Error("Invalid exec code " + code); |
331 | 1 | err.code = code; |
332 | 1 | return next(err); |
333 | | } |
334 | 16 | return next(); |
335 | | }, 1); |
336 | | }); |
337 | | }; |
338 | 18 | return conditions.all(option, next, cmd); |
339 | | }).on('both', function(err) { |
340 | 18 | if (!isArray) { |
341 | 18 | stdouts = stdouts[0]; |
342 | | } |
343 | 18 | if (!isArray) { |
344 | 18 | stderrs = stderrs[0]; |
345 | | } |
346 | 18 | return callback(err, executed, stdouts, stderrs); |
347 | | }); |
348 | | }, |
349 | | /* |
350 | | |
351 | | `extract(options, callback)` |
352 | | ---------------------------- |
353 | | |
354 | | Extract an archive. Multiple compression types are supported. Unless |
355 | | specified asan option, format is derived from the source extension. At the |
356 | | moment, supported extensions are '.tgz', '.tar.gz' and '.zip'. |
357 | | |
358 | | `options` Command options include: |
359 | | |
360 | | * `source` Archive to decompress. |
361 | | * `destination` Default to the source parent directory. |
362 | | * `format` One of 'tgz' or 'zip'. |
363 | | * `creates` Ensure the given file is created or an error is send in the callback. |
364 | | * `not_if_exists` Cancel extraction if file exists. |
365 | | |
366 | | `callback` Received parameters are: |
367 | | |
368 | | * `err` Error object if any. |
369 | | * `extracted` Number of extracted archives. |
370 | | */ |
371 | | |
372 | | extract: function(options, callback) { |
373 | 10 | var extracted; |
374 | 10 | options = misc.options(options); |
375 | 10 | extracted = 0; |
376 | 10 | return each(options).on('item', function(next, options) { |
377 | 10 | var creates, destination, ext, extract, format, success, _ref1; |
378 | 10 | if (!options.source) { |
379 | 0 | return next(new Error("Missing source: " + options.source)); |
380 | | } |
381 | 10 | destination = (_ref1 = options.destination) != null ? _ref1 : path.dirname(options.source); |
382 | 10 | if (options.format != null) { |
383 | 0 | format = options.format; |
384 | | } else { |
385 | 10 | if (/\.(tar\.gz|tgz)$/.test(options.source)) { |
386 | 5 | format = 'tgz'; |
387 | 5 | } else if (/\.zip$/.test(options.source)) { |
388 | 4 | format = 'zip'; |
389 | | } else { |
390 | 1 | ext = path.extname(options.source); |
391 | 1 | return next(new Error("Unsupported extension, got " + (JSON.stringify(ext)))); |
392 | | } |
393 | | } |
394 | 9 | extract = function() { |
395 | 8 | var cmd; |
396 | 8 | cmd = null; |
397 | 8 | switch (format) { |
398 | | case 'tgz': |
399 | 4 | cmd = "tar xzf " + options.source + " -C " + destination; |
400 | 4 | break; |
401 | | case 'zip': |
402 | 4 | cmd = "unzip -u " + options.source + " -d " + destination; |
403 | | } |
404 | 8 | return exec(cmd, function(err, stdout, stderr) { |
405 | 8 | if (err) { |
406 | 0 | return next(err); |
407 | | } |
408 | 8 | return creates(); |
409 | | }); |
410 | | }; |
411 | 9 | creates = function() { |
412 | 8 | if (options.creates == null) { |
413 | 6 | return success(); |
414 | | } |
415 | 2 | return fs.exists(options.creates, function(exists) { |
416 | 2 | if (!exists) { |
417 | 1 | return next(new Error("Failed to create '" + (path.basename(options.creates)) + "'")); |
418 | | } |
419 | 1 | return success(); |
420 | | }); |
421 | | }; |
422 | 9 | success = function() { |
423 | 7 | extracted++; |
424 | 7 | return next(); |
425 | | }; |
426 | 9 | return conditions.all(options, next, extract); |
427 | | }).on('both', function(err) { |
428 | 10 | return callback(err, extracted); |
429 | | }); |
430 | | }, |
431 | | /* |
432 | | |
433 | | `git` |
434 | | ----- |
435 | | |
436 | | `options` Command options include: |
437 | | |
438 | | * `source` Git source repository address. |
439 | | * `destination` Directory where to clone the repository. |
440 | | * `revision` Git revision, branch or tag. |
441 | | */ |
442 | | |
443 | | git: function(options, callback) { |
444 | 5 | var updated; |
445 | 5 | options = misc.options(options); |
446 | 5 | updated = 0; |
447 | 5 | return each(options).on('item', function(next, options) { |
448 | 5 | var checkout, clone, log, prepare, rev, _ref1; |
449 | 5 | if ((_ref1 = options.revision) == null) { |
450 | 3 | options.revision = 'HEAD'; |
451 | | } |
452 | 5 | rev = null; |
453 | 5 | prepare = function() { |
454 | 5 | return fs.stat(options.destination, function(err, stat) { |
455 | 5 | var gitDir; |
456 | 5 | if (err && err.code === 'ENOENT') { |
457 | 2 | return clone(); |
458 | | } |
459 | 3 | if (!stat.isDirectory()) { |
460 | 0 | return next(new Error("Destination not a directory, got " + options.destination)); |
461 | | } |
462 | 3 | gitDir = "" + options.destination + "/.git"; |
463 | 3 | return fs.stat(gitDir, function(err, stat) { |
464 | 3 | if (err || !stat.isDirectory()) { |
465 | 0 | return next(err); |
466 | | } |
467 | 3 | return log(); |
468 | | }); |
469 | | }); |
470 | | }; |
471 | 5 | clone = function() { |
472 | 2 | return mecano.exec({ |
473 | | cmd: "git clone " + options.source + " " + (path.basename(options.destination)), |
474 | | cwd: path.dirname(options.destination) |
475 | | }, function(err, executed, stdout, stderr) { |
476 | 2 | if (err) { |
477 | 0 | return next(err); |
478 | | } |
479 | 2 | return checkout(); |
480 | | }); |
481 | | }; |
482 | 5 | log = function() { |
483 | 3 | return mecano.exec({ |
484 | | cmd: "git log --pretty=format:'%H' -n 1", |
485 | | cwd: options.destination |
486 | | }, function(err, executed, stdout, stderr) { |
487 | 3 | var current; |
488 | 3 | if (err) { |
489 | 0 | return next(err); |
490 | | } |
491 | 3 | current = stdout.trim(); |
492 | 3 | return mecano.exec({ |
493 | | cmd: "git rev-list --max-count=1 " + options.revision, |
494 | | cwd: options.destination |
495 | | }, function(err, executed, stdout, stderr) { |
496 | 3 | if (err) { |
497 | 0 | return next(err); |
498 | | } |
499 | 3 | if (stdout.trim() !== current) { |
500 | 1 | return checkout(); |
501 | | } else { |
502 | 2 | return next(); |
503 | | } |
504 | | }); |
505 | | }); |
506 | | }; |
507 | 5 | checkout = function() { |
508 | 3 | return mecano.exec({ |
509 | | cmd: "git checkout " + options.revision, |
510 | | cwd: options.destination |
511 | | }, function(err) { |
512 | 3 | if (err) { |
513 | 0 | return next(err); |
514 | | } |
515 | 3 | updated++; |
516 | 3 | return next(); |
517 | | }); |
518 | | }; |
519 | 5 | return conditions.all(options, next, prepare); |
520 | | }).on('both', function(err) { |
521 | 5 | return callback(err, updated); |
522 | | }); |
523 | | }, |
524 | | /* |
525 | | |
526 | | `ln` `link(options, callback)` |
527 | | ------------------------------ |
528 | | Create a symbolic link and it's parent directories if they don't yet |
529 | | exist. |
530 | | |
531 | | `options` Command options include: |
532 | | |
533 | | * `source` Referenced file to be linked. |
534 | | * `destination` Symbolic link to be created. |
535 | | * `exec` Create an executable file with an `exec` command. |
536 | | * `chmod` Default to 0755. |
537 | | |
538 | | `callback` Received parameters are: |
539 | | |
540 | | * `err` Error object if any. |
541 | | * `linked` Number of created links. |
542 | | */ |
543 | | |
544 | | link: function(options, callback) { |
545 | 8 | var exec_create, exec_exists, linked, option, parents, sym_create, sym_exists; |
546 | 8 | options = misc.options(options); |
547 | 8 | linked = 0; |
548 | 8 | sym_exists = function(option, callback) { |
549 | 7 | return fs.exists(option.destination, function(exists) { |
550 | 7 | if (!exists) { |
551 | 5 | return callback(null, false); |
552 | | } |
553 | 2 | return fs.readlink(option.destination, function(err, resolvedPath) { |
554 | 2 | if (err) { |
555 | 0 | return callback(err); |
556 | | } |
557 | 2 | if (resolvedPath === option.source) { |
558 | 2 | return callback(null, true); |
559 | | } |
560 | 0 | return fs.unlink(option.destination, function(err) { |
561 | 0 | if (err) { |
562 | 0 | return callback(err); |
563 | | } |
564 | 0 | return callback(null, false); |
565 | | }); |
566 | | }); |
567 | | }); |
568 | | }; |
569 | 8 | sym_create = function(option, callback) { |
570 | 5 | return fs.symlink(option.source, option.destination, function(err) { |
571 | 5 | if (err) { |
572 | 0 | return callback(err); |
573 | | } |
574 | 5 | linked++; |
575 | 5 | return callback(); |
576 | | }); |
577 | | }; |
578 | 8 | exec_exists = function(option, callback) { |
579 | 0 | return fs.exists(option.destination, function(exists) { |
580 | 0 | if (!exists) { |
581 | 0 | return callback(null, false); |
582 | | } |
583 | 0 | return fs.readFile(option.destination, 'ascii', function(err, content) { |
584 | 0 | var exec_cmd; |
585 | 0 | if (err) { |
586 | 0 | return callback(err); |
587 | | } |
588 | 0 | exec_cmd = /exec (.*) \$@/.exec(content)[1]; |
589 | 0 | return callback(null, exec_cmd && exec_cmd === option.source); |
590 | | }); |
591 | | }); |
592 | | }; |
593 | 8 | exec_create = function(option, callback) { |
594 | 0 | var content; |
595 | 0 | content = "#!/bin/bash\nexec " + option.source + " $@"; |
596 | 0 | return fs.writeFile(option.destination, content, function(err) { |
597 | 0 | if (err) { |
598 | 0 | return callback(err); |
599 | | } |
600 | 0 | return fs.chmod(option.destination, option.chmod, function(err) { |
601 | 0 | if (err) { |
602 | 0 | return callback(err); |
603 | | } |
604 | 0 | linked++; |
605 | 0 | return callback(); |
606 | | }); |
607 | | }); |
608 | | }; |
609 | 8 | parents = (function() { |
610 | 8 | var _i, _len, _results; |
611 | 8 | _results = []; |
612 | 8 | for (_i = 0, _len = options.length; _i < _len; _i++) { |
613 | 9 | option = options[_i]; |
614 | 9 | _results.push(path.normalize(path.dirname(option.destination))); |
615 | | } |
616 | 8 | return _results; |
617 | | })(); |
618 | 8 | return mecano.mkdir(parents, function(err, created) { |
619 | 8 | if (err) { |
620 | 0 | return callback(err); |
621 | | } |
622 | 8 | return each(options).parallel(true).on('item', function(next, option) { |
623 | 9 | var dispatch, _ref1; |
624 | 9 | if (!option.source) { |
625 | 1 | return next(new Error("Missing source, got " + (JSON.stringify(option.source)))); |
626 | | } |
627 | 8 | if (!option.destination) { |
628 | 1 | return next(new Error("Missing destination, got " + (JSON.stringify(option.destination)))); |
629 | | } |
630 | 7 | if ((_ref1 = option.chmod) == null) { |
631 | 7 | option.chmod = 0x1ed; |
632 | | } |
633 | 7 | dispatch = function() { |
634 | 7 | if (option.exec) { |
635 | 0 | return exec_exists(option, function(err, exists) { |
636 | 0 | if (exists) { |
637 | 0 | return next(); |
638 | | } |
639 | 0 | return exec_create(option, next); |
640 | | }); |
641 | | } else { |
642 | 7 | return sym_exists(option, function(err, exists) { |
643 | 7 | if (exists) { |
644 | 2 | return next(); |
645 | | } |
646 | 5 | return sym_create(option, next); |
647 | | }); |
648 | | } |
649 | | }; |
650 | 7 | return dispatch(); |
651 | | }).on('both', function(err) { |
652 | 8 | return callback(err, linked); |
653 | | }); |
654 | | }); |
655 | | }, |
656 | | /* |
657 | | |
658 | | `mkdir(options, callback)` |
659 | | -------------------------- |
660 | | |
661 | | Recursively create a directory. The behavior is similar to the Unix command `mkdir -p`. |
662 | | It supports an alternative syntax where options is simply the path of the directory |
663 | | to create. |
664 | | |
665 | | `options` Command options include: |
666 | | |
667 | | * `source` Path or array of paths. |
668 | | * `directory` Shortcut for `source` |
669 | | * `exclude` Regular expression. |
670 | | * `chmod` Default to 0755. |
671 | | * `cwd` Current working directory for relative paths. |
672 | | |
673 | | `callback` Received parameters are: |
674 | | |
675 | | * `err` Error object if any. |
676 | | * `created` Number of created directories |
677 | | |
678 | | Simple usage: |
679 | | |
680 | | mecano.mkdir './some/dir', (err, created) -> |
681 | | console.log err?.message ? created |
682 | | */ |
683 | | |
684 | | mkdir: function(options, callback) { |
685 | 38 | var created; |
686 | 38 | options = misc.options(options); |
687 | 38 | created = 0; |
688 | 38 | return each(options).on('item', function(next, option) { |
689 | 39 | var check, create, cwd, _ref1; |
690 | 39 | if (typeof option === 'string') { |
691 | 34 | option = { |
692 | | source: option |
693 | | }; |
694 | | } |
695 | 39 | if (!(option.source != null) && (option.directory != null)) { |
696 | 5 | option.source = option.directory; |
697 | | } |
698 | 39 | cwd = (_ref1 = option.cwd) != null ? _ref1 : process.cwd(); |
699 | 39 | option.source = path.resolve(cwd, option.source); |
700 | 39 | if (option.source == null) { |
701 | 0 | return next(new Error('Missing source option')); |
702 | | } |
703 | 39 | check = function() { |
704 | 39 | return fs.stat(option.source, function(err, stat) { |
705 | 39 | if (err && err.code === 'ENOENT') { |
706 | 31 | return create(); |
707 | | } |
708 | 8 | if (err) { |
709 | 0 | return next(err); |
710 | | } |
711 | 8 | if (stat.isDirectory()) { |
712 | 8 | return next(); |
713 | | } |
714 | 0 | return next(err('Invalid source, got #{JSON.encode(option.source)}')); |
715 | | }); |
716 | | }; |
717 | 39 | create = function() { |
718 | 31 | var current, dirCreated, dirs, _ref2; |
719 | 31 | if ((_ref2 = option.chmod) == null) { |
720 | 31 | option.chmod = 0x1ed; |
721 | | } |
722 | 31 | current = ''; |
723 | 31 | dirCreated = false; |
724 | 31 | dirs = option.source.split('/'); |
725 | 31 | return each(dirs).on('item', function(next, dir) { |
726 | 290 | if ((option.exclude != null) && option.exclude instanceof RegExp) { |
727 | 12 | if (option.exclude.test(dir)) { |
728 | 1 | return next(); |
729 | | } |
730 | | } |
731 | 289 | current += "/" + dir; |
732 | 289 | return fs.exists(current, function(exists) { |
733 | 289 | if (exists) { |
734 | 255 | return next(); |
735 | | } |
736 | 34 | return fs.mkdir(current, option.chmod, function(err) { |
737 | 34 | if (err) { |
738 | 0 | return next(err); |
739 | | } |
740 | 34 | dirCreated = true; |
741 | 34 | return next(); |
742 | | }); |
743 | | }); |
744 | | }).on('both', function(err) { |
745 | 31 | if (dirCreated) { |
746 | 31 | created++; |
747 | | } |
748 | 31 | return next(err); |
749 | | }); |
750 | | }; |
751 | 39 | return check(); |
752 | | }).on('both', function(err) { |
753 | 38 | return callback(err, created); |
754 | | }); |
755 | | }, |
756 | | /* |
757 | | |
758 | | `rm` `remove(options, callback)` |
759 | | -------------------------------- |
760 | | |
761 | | Recursively remove a file or directory. Internally, the function |
762 | | use the [rimraf](https://github.com/isaacs/rimraf) library. |
763 | | |
764 | | `options` Command options include: |
765 | | |
766 | | * `source` File or directory. |
767 | | |
768 | | `callback` Received parameters are: |
769 | | |
770 | | * `err` Error object if any. |
771 | | * `deleted` Number of deleted sources. |
772 | | |
773 | | Example |
774 | | |
775 | | mecano.rm './some/dir', (err, removed) -> |
776 | | console.log "#{removed} dir removed" |
777 | | |
778 | | Removing a directory unless a given file exists |
779 | | |
780 | | mecano.rm |
781 | | source: './some/dir' |
782 | | not_if_exists: './some/file' |
783 | | , (err, removed) -> |
784 | | console.log "#{removed} dir removed" |
785 | | |
786 | | Removing multiple files and directories |
787 | | |
788 | | mecano.rm [ |
789 | | { source: './some/dir', not_if_exists: './some/file' } |
790 | | './some/file' |
791 | | ], (err, removed) -> |
792 | | console.log "#{removed} dirs removed" |
793 | | */ |
794 | | |
795 | | remove: function(options, callback) { |
796 | 26 | var deleted; |
797 | 26 | options = misc.options(options); |
798 | 26 | deleted = 0; |
799 | 26 | return each(options).on('item', function(next, options) { |
800 | 26 | if (typeof options === 'string') { |
801 | 25 | options = { |
802 | | source: options |
803 | | }; |
804 | | } |
805 | 26 | if (options.source == null) { |
806 | 0 | return next(new Error('Missing source: #{option.source}')); |
807 | | } |
808 | 26 | return fs.lstat(options.source, function(err, stat) { |
809 | 26 | var _ref1; |
810 | 26 | if (err) { |
811 | 0 | return next(); |
812 | | } |
813 | 26 | if ((_ref1 = options.options) == null) { |
814 | 26 | options.options = {}; |
815 | | } |
816 | 26 | return rimraf(options.source, function(err) { |
817 | 26 | if (err) { |
818 | 0 | return next(err); |
819 | | } |
820 | 26 | deleted++; |
821 | 26 | return next(); |
822 | | }); |
823 | | }); |
824 | | }).on('both', function(err) { |
825 | 26 | return callback(err, deleted); |
826 | | }); |
827 | | }, |
828 | | /* |
829 | | |
830 | | `render(options, callback)` |
831 | | --------------------------- |
832 | | |
833 | | Render a template file At the moment, only the |
834 | | [ECO](http://github.com/sstephenson/eco) templating engine is integrated. |
835 | | |
836 | | `options` Command options include: |
837 | | |
838 | | * `engine` Template engine to use, default to "eco" |
839 | | * `content` Templated content, bypassed if source is provided. |
840 | | * `source` File path where to extract content from. |
841 | | * `destination` File path where to write content to. |
842 | | * `context` Map of key values to inject into the template. |
843 | | |
844 | | `callback` Received parameters are: |
845 | | |
846 | | * `err` Error object if any. |
847 | | * `rendered` Number of rendered files. |
848 | | */ |
849 | | |
850 | | render: function(options, callback) { |
851 | 3 | var rendered; |
852 | 3 | options = misc.options(options); |
853 | 3 | rendered = 0; |
854 | 3 | return each(options).on('item', function(next, option) { |
855 | 3 | var readSource, writeContent; |
856 | 3 | if (!(option.source || option.content)) { |
857 | 0 | return next(new Error('Missing source or content')); |
858 | | } |
859 | 3 | if (!option.destination) { |
860 | 0 | return next(new Error('Missing destination')); |
861 | | } |
862 | 3 | readSource = function() { |
863 | 3 | if (!option.source) { |
864 | 1 | return writeContent(); |
865 | | } |
866 | 2 | return fs.exists(option.source, function(exists) { |
867 | 2 | if (!exists) { |
868 | 1 | return next(new Error("Invalid source, got " + (JSON.stringify(option.source)))); |
869 | | } |
870 | 1 | return fs.readFile(option.source, function(err, content) { |
871 | 1 | if (err) { |
872 | 0 | return next(err); |
873 | | } |
874 | 1 | option.content = content; |
875 | 1 | return writeContent(); |
876 | | }); |
877 | | }); |
878 | | }; |
879 | 3 | writeContent = function() { |
880 | 2 | var content; |
881 | 2 | try { |
882 | 2 | content = eco.render(option.content.toString(), option.context || {}); |
883 | | } catch (err) { |
884 | 0 | return next(err); |
885 | | } |
886 | 2 | return fs.writeFile(option.destination, content, function(err) { |
887 | 2 | if (err) { |
888 | 0 | return next(err); |
889 | | } |
890 | 2 | rendered++; |
891 | 2 | return next(); |
892 | | }); |
893 | | }; |
894 | 3 | return readSource(); |
895 | | }).on('both', function(err) { |
896 | 3 | return callback(err, rendered); |
897 | | }); |
898 | | } |
899 | | }; |
900 | | |
901 | 1 | mecano.cp = mecano.copy; |
902 | | |
903 | 1 | mecano.exec = mecano.execute; |
904 | | |
905 | 1 | mecano.ln = mecano.link; |
906 | | |
907 | 1 | mecano.rm = mecano.remove; |