| 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; |