shelljs/shell.js

1208 строки
31 KiB
JavaScript

//
// shell.js
// Unix shell commands on top of Node's API
//
// Copyright (c) 2012 Artur Adib
// http://github.com/arturadib/shell.js
//
var fs = require('fs'),
path = require('path'),
util = require('util'),
vm = require('vm'),
child = require('child_process'),
os = require('os');
// Node shims for < v0.7
fs.existsSync = fs.existsSync || path.existsSync;
var state = {
error: null,
fatal: false,
silent: false,
currentCmd: 'shell.js',
tempDir: null
},
platform = os.type().match(/^Win/) ? 'win' : 'unix';
//@
//@ All commands run synchronously, unless otherwise stated.
//@
//@
//@ #### cd('dir')
//@ Changes to directory `dir` for the duration of the script
function _cd(options, dir) {
if (!dir)
error('directory not specified');
if (!fs.existsSync(dir))
error('no such file or directory: ' + dir);
if (fs.existsSync(dir) && !fs.statSync(dir).isDirectory())
error('not a directory: ' + dir);
process.chdir(dir);
};
exports.cd = wrap('cd', _cd);
//@
//@ #### pwd()
//@ Returns the current directory.
function _pwd(options) {
var pwd = path.resolve(process.cwd());
return ShellString(pwd);
};
exports.pwd = wrap('pwd', _pwd);
//@
//@ #### ls([options ,] path [,path ...])
//@ #### ls([options ,] path_array)
//@ Available options:
//@
//@ + `-R`: recursive
//@ + `-a`: all files (include files beginning with `.`)
//@
//@ Examples:
//@
//@ ```javascript
//@ ls('projs/*.js');
//@ ls('-R', '/users/me', '/tmp');
//@ ls('-R', ['/users/me', '/tmp']); // same as above
//@ ```
//@
//@ Returns list of files in the given path, or in current directory if no path provided.
//@ For convenient iteration via `for (file in ls())`, the format returned is a hash object:
//@ `{ 'file1':null, 'dir1/file2':null, ...}`.
function _ls(options, paths) {
options = parseOptions(options, {
'R': 'recursive',
'a': 'all'
});
if (!paths)
paths = ['.'];
else if (typeof paths === 'object')
paths = paths; // assume array
else if (typeof paths === 'string')
paths = [].slice.call(arguments, 1);
var hash = {};
function pushHash(file, query) {
// hidden file?
if (path.basename(file)[0] === '.') {
// not explicitly asking for hidden files?
if (!options.all && !(path.basename(query)[0] === '.' && path.basename(query).length > 1))
return;
}
hash[file] = null;
}
paths.forEach(function(p) {
if (fs.existsSync(p)) {
// Simple file?
if (fs.statSync(p).isFile()) {
pushHash(p, p);
return; // continue
}
// Simple dir?
if (fs.statSync(p).isDirectory()) {
// Iterate over p contents
fs.readdirSync(p).forEach(function(file) {
pushHash(file, p);
// Recursive
var oldDir = _pwd();
_cd('', p);
if (fs.statSync(file).isDirectory() && options.recursive)
hash = extend(hash, _ls('-R', file+'/*'));
_cd('', oldDir);
});
return; // continue
}
}
// p does not exist - possible wildcard present
var basename = path.basename(p);
var dirname = path.dirname(p);
// Wildcard present on an existing dir? (e.g. '/tmp/*.js')
if (basename.search(/\*/) > -1 && fs.existsSync(dirname) && fs.statSync(dirname).isDirectory) {
// Escape special regular expression chars
var regexp = basename.replace(/(\^|\$|\(|\)|\<|\>|\[|\]|\{|\}|\.|\+|\?)/g, '\\$1');
// Translates wildcard into regex
regexp = '^' + regexp.replace(/\*/g, '.*');
// Iterate over directory contents
fs.readdirSync(dirname).forEach(function(file) {
if (file.match(new RegExp(regexp))) {
pushHash(path.normalize(dirname+'/'+file), basename);
// Recursive
var pp = dirname + '/' + file;
if (fs.statSync(pp).isDirectory() && options.recursive)
hash = extend(hash, _ls('-R', pp+'/*'));
}
}); // forEach
return;
}
error('no such file or directory: ' + p, true);
});
return hash;
};
exports.ls = wrap('ls', _ls);
//@
//@ #### cp('[options ,] source [,source ...], dest')
//@ #### cp('[options ,] source_array, dest')
//@ Available options:
//@
//@ + `-f`: force
//@ + `-r, -R`: recursive
//@
//@ Examples:
//@
//@ ```javascript
//@ cp('file1', 'dir1');
//@ cp('-Rf', '/tmp/*', '/usr/local/*', '/home/tmp');
//@ cp('-Rf', ['/tmp/*', '/usr/local/*'], '/home/tmp'); // same as above
//@ ```
//@
//@ Copies files. The wildcard `*` is accepted.
function _cp(options, sources, dest) {
options = parseOptions(options, {
'f': 'force',
'R': 'recursive',
'r': 'recursive'
});
// Get sources, dest
if (arguments.length < 3) {
error('missing <source> and/or <dest>');
} else if (arguments.length > 3) {
sources = [].slice.call(arguments, 1, arguments.length - 1);
dest = arguments[arguments.length - 1];
} else if (typeof sources === 'string') {
sources = [sources];
} else if ('length' in sources) {
sources = sources; // no-op for array
} else {
error('invalid arguments');
}
// Dest is not existing dir, but multiple sources given
if ((!fs.existsSync(dest) || !fs.statSync(dest).isDirectory()) && sources.length > 1)
error('dest is not a directory (too many sources)');
// Dest is an existing file, but no -f given
if (fs.existsSync(dest) && fs.statSync(dest).isFile() && !options.force)
error('dest file already exists: ' + dest);
sources = expand(sources);
sources.forEach(function(src) {
if (!fs.existsSync(src)) {
error('no such file or directory: '+src, true);
return; // skip file
}
// If here, src exists
if (fs.statSync(src).isDirectory()) {
if (!options.recursive) {
// Non-Recursive
log(src + ' is a directory (not copied)');
} else {
// Recursive
// 'cp /a/source dest' should create 'source' in 'dest'
var newDest = dest+'/'+path.basename(src),
checkDir = fs.statSync(src);
try {
fs.mkdirSync(newDest, checkDir.mode);
} catch (e) {
//if the directory already exists, that's okay
if (e.code !== 'EEXIST') throw e;
}
cpdirSyncRecursive(src, newDest, {force: options.force});
}
return; // done with dir
}
// If here, src is a file
// When copying to '/path/dir':
// thisDest = '/path/dir/file1'
var thisDest = dest;
if (fs.existsSync(dest) && fs.statSync(dest).isDirectory())
thisDest = path.normalize(dest + '/' + path.basename(src));
if (fs.existsSync(thisDest) && !options.force) {
error('dest file already exists: ' + thisDest, true);
return; // skip file
}
copyFileSync(src, thisDest);
}); // forEach(src)
}; // cp
exports.cp = wrap('cp', _cp);
//@
//@ #### rm([options ,] file [, file ...])
//@ #### rm([options ,] file_array)
//@ Available options:
//@
//@ + `-f`: force
//@ + `-r, -R`: recursive
//@
//@ Examples:
//@
//@ ```javascript
//@ rm('-rf', '/tmp/*');
//@ rm('some_file.txt', 'another_file.txt');
//@ rm(['some_file.txt', 'another_file.txt']); // same as above
//@ ```
//@
//@ Removes files. The wildcard `*` is accepted.
function _rm(options, files) {
options = parseOptions(options, {
'f': 'force',
'r': 'recursive',
'R': 'recursive'
});
if (!files)
error('no paths given');
if (typeof files === 'string')
files = [].slice.call(arguments, 1);
// if it's array leave it as it is
files = expand(files);
files.forEach(function(file) {
if (!fs.existsSync(file)) {
// Path does not exist, no force flag given
if (!options.force)
error('no such file or directory: '+file, true);
return; // skip file
}
// If here, path exists
// Remove simple file
if (fs.statSync(file).isFile()) {
fs.unlinkSync(file);
return;
}
// Path is an existing directory, but no -r flag given
if (fs.statSync(file).isDirectory() && !options.recursive) {
error('path is a directory', true);
return; // skip path
}
// Recursively remove existing directory
if (fs.statSync(file).isDirectory() && options.recursive) {
rmdirSyncRecursive(file);
}
}); // forEach(file)
}; // rm
exports.rm = wrap('rm', _rm);
//@
//@ #### mv(source [, source ...], dest')
//@ #### mv(source_array, dest')
//@ Available options:
//@
//@ + `f`: force
//@
//@ Examples:
//@
//@ ```javascript
//@ mv('-f', 'file', 'dir/');
//@ mv('file1', 'file2', 'dir/');
//@ mv(['file1', 'file2'], 'dir/'); // same as above
//@ ```
//@
//@ Moves files. The wildcard `*` is accepted.
function _mv(options, sources, dest) {
options = parseOptions(options, {
'f': 'force'
});
// Get sources, dest
if (arguments.length < 3) {
error('missing <source> and/or <dest>');
} else if (arguments.length > 3) {
sources = [].slice.call(arguments, 1, arguments.length - 1);
dest = arguments[arguments.length - 1];
} else if (typeof sources === 'string') {
sources = [sources];
} else if ('length' in sources) {
sources = sources; // no-op for array
} else {
error('invalid arguments');
}
sources = expand(sources);
// Dest is not existing dir, but multiple sources given
if ((!fs.existsSync(dest) || !fs.statSync(dest).isDirectory()) && sources.length > 1)
error('dest is not a directory (too many sources)');
// Dest is an existing file, but no -f given
if (fs.existsSync(dest) && fs.statSync(dest).isFile() && !options.force)
error('dest file already exists: ' + dest);
sources.forEach(function(src) {
if (!fs.existsSync(src)) {
error('no such file or directory: '+src, true);
return; // skip file
}
// If here, src exists
// When copying to '/path/dir':
// thisDest = '/path/dir/file1'
var thisDest = dest;
if (fs.existsSync(dest) && fs.statSync(dest).isDirectory())
thisDest = path.normalize(dest + '/' + path.basename(src));
if (fs.existsSync(thisDest) && !options.force) {
error('dest file already exists: ' + thisDest, true);
return; // skip file
}
if (path.resolve(src) === path.dirname(path.resolve(thisDest))) {
error('cannot move to self: '+src, true);
return; // skip file
}
fs.renameSync(src, thisDest);
}); // forEach(src)
}; // mv
exports.mv = wrap('mv', _mv);
//@
//@ #### mkdir([options ,] dir [, dir ...])
//@ #### mkdir([options ,] dir_array)
//@ Available options:
//@
//@ + `p`: full path (will create intermediate dirs if necessary)
//@
//@ Examples:
//@
//@ ```javascript
//@ mkdir('-p', '/tmp/a/b/c/d', '/tmp/e/f/g');
//@ mkdir('-p', ['/tmp/a/b/c/d', '/tmp/e/f/g']); // same as above
//@ ```
//@
//@ Creates directories.
function _mkdir(options, dirs) {
options = parseOptions(options, {
'p': 'fullpath'
});
if (!dirs)
error('no paths given');
if (typeof dirs === 'string')
dirs = [].slice.call(arguments, 1);
// if it's array leave it as it is
dirs.forEach(function(dir) {
if (fs.existsSync(dir)) {
if (!options.fullpath)
error('path already exists: ' + dir, true);
return; // skip dir
}
// Base dir does not exist, and no -p option given
var baseDir = path.dirname(dir);
if (!fs.existsSync(baseDir) && !options.fullpath) {
error('no such file or directory: ' + baseDir, true);
return; // skip dir
}
if (options.fullpath)
mkdirSyncRecursive(dir);
else
fs.mkdirSync(dir, 0777);
});
}; // mkdir
exports.mkdir = wrap('mkdir', _mkdir);
//@
//@ #### cat(file [, file ...])
//@ #### cat(file_array)
//@
//@ Examples:
//@
//@ ```javascript
//@ var str = cat('file*.txt');
//@ var str = cat('file1', 'file2');
//@ var str = cat(['file1', 'file2']); // same as above
//@ ```
//@
//@ Returns a string containing the given file, or a concatenated string
//@ containing the files if more than one file is given (a new line character is
//@ introduced between each file). Wildcard `*` accepted.
function _cat(options, files) {
var cat = '';
if (!files)
error('no paths given');
if (typeof files === 'string')
files = [].slice.call(arguments, 1);
// if it's array leave it as it is
files = expand(files);
files.forEach(function(file) {
if (!fs.existsSync(file))
error('no such file or directory: ' + file);
cat += fs.readFileSync(file, 'utf8') + '\n';
});
if (cat[cat.length-1] === '\n')
cat = cat.substring(0, cat.length-1);
return ShellString(cat);
};
exports.cat = wrap('cat', _cat);
//@
//@ #### 'string'.to(file)
//@
//@ Examples:
//@
//@ ```javascript
//@ cat('input.txt').to('output.txt');
//@ ```
//@
//@ Analogous to the redirection operator `>` in Unix, but works with JavaScript strings (such as
//@ those returned by `cat`, `grep`, etc). _Like Unix redirections, `to()` will overwrite any existing file!_
function _to(options, file) {
if (!file)
error('wrong arguments');
if (!fs.existsSync( path.dirname(file) ))
error('no such file or directory: ' + path.dirname(file));
fs.writeFileSync(file, this.toString(), 'utf8');
};
// In the future, when Proxies are default, we can add methods like `.to()` to primitive strings.
// For now, this is a dummy function to bookmark places we need such strings
function ShellString(str) {
return str;
}
String.prototype.to = wrap('to', _to);
//@
//@ #### sed([options ,] search_regex, replace_str, file)
//@ Available options:
//@
//@ + `-i`: Replace contents of 'file' in-place. _Note that no backups will be created!_
//@
//@ Examples:
//@
//@ ```javascript
//@ sed('-i', 'PROGRAM_VERSION', 'v0.1.3', 'source.js');
//@ sed(/.*DELETE_THIS_LINE.*\n/, '', 'source.js');
//@ ```
//@
//@ Reads an input string from `file` and performs a JavaScript `replace()` on the input
//@ using the given search regex and replacement string. Returns the new string after replacement.
function _sed(options, regex, replacement, file) {
options = parseOptions(options, {
'i': 'inplace'
});
if (typeof replacement === 'string')
replacement = replacement; // no-op
else if (typeof replacement === 'number')
replacement = replacement.toString(); // fallback
else
error('invalid replacement string');
if (!file)
error('no file given');
if (!fs.existsSync(file))
error('no such file or directory: ' + file);
var result = fs.readFileSync(file, 'utf8').replace(regex, replacement);
if (options.inplace)
fs.writeFileSync(file, result, 'utf8');
return ShellString(result);
};
exports.sed = wrap('sed', _sed);
//@
//@ #### grep(regex_filter, file [, file ...])
//@ #### grep(regex_filter, file_array)
//@
//@ Examples:
//@
//@ ```javascript
//@ grep('GLOBAL_VARIABLE', '*.js');
//@ ```
//@
//@ Reads input string from given files and returns a string containing all lines of the
//@ file that match the given `regex_filter`. Wildcard `*` accepted.
function _grep(options, regex, files) {
if (!files)
error('no paths given');
if (typeof files === 'string')
files = [].slice.call(arguments, 2);
// if it's array leave it as it is
files = expand(files);
var grep = '';
files.forEach(function(file) {
if (!fs.existsSync(file)) {
error('no such file or directory: ' + file, true);
return;
}
var contents = fs.readFileSync(file, 'utf8'),
lines = contents.split(/\r*\n/);
lines.forEach(function(line) {
if (line.match(regex))
grep += line + '\n';
});
});
return ShellString(grep);
};
exports.grep = wrap('grep', _grep);
//@
//@ #### which(command)
//@
//@ Examples:
//@
//@ ```javascript
//@ var nodeExec = which('node');
//@ ```
//@
//@ Searches for `command` in the system's PATH. On Windows looks for `.exe`, `.cmd`, and `.bat` extensions.
//@ Returns string containing the absolute path to the command.
function _which(options, cmd) {
if (!cmd)
error('must specify command');
var pathEnv = process.env.path || process.env.Path || process.env.PATH,
pathArray = splitPath(pathEnv),
where = null;
// No relative/absolute paths provided?
if (cmd.search(/\//) === -1) {
// Search for command in PATH
pathArray.forEach(function(dir) {
if (where)
return; // already found it
var attempt = path.resolve(dir + '/' + cmd);
if (fs.existsSync(attempt)) {
where = attempt;
return;
}
if (platform === 'win') {
var baseAttempt = attempt;
attempt = baseAttempt + '.exe';
if (fs.existsSync(attempt)) {
where = attempt;
return;
}
attempt = baseAttempt + '.cmd';
if (fs.existsSync(attempt)) {
where = attempt;
return;
}
attempt = baseAttempt + '.bat';
if (fs.existsSync(attempt)) {
where = attempt;
return;
}
} // if 'win'
});
}
// Command not found anywhere?
if (!fs.existsSync(cmd) && !where)
return null;
where = where || path.resolve(cmd);
return ShellString(where);
};
exports.which = wrap('which', _which);
//@
//@ #### echo(string [,string ...])
//@
//@ Examples:
//@
//@ ```javascript
//@ echo('hello world');
//@ var str = echo('hello world');
//@ ```
//@
//@ Prints string to stdout, and returns string with additional utility methods
//@ like `.to()`.
function _echo(options) {
var messages = [].slice.call(arguments, 1);
log.apply(this, messages);
return ShellString(messages.join(' '));
};
exports.echo = wrap('echo', _echo);
//@
//@ #### exit(code)
//@ Exits the current process with the given exit code.
exports.exit = process.exit;
//@
//@ #### env['VAR_NAME']
//@ Object containing environment variables (both getter and setter). Shortcut to process.env.
exports.env = process.env;
//@
//@ #### exec(command [, options] [, callback])
//@ Available options (all `false` by default):
//@
//@ + `async`: Asynchronous execution. Needs callback.
//@ + `silent`: Do not echo program output to console.
//@
//@ Examples:
//@
//@ ```javascript
//@ var version = exec('node --version', {silent:true}).output;
//@ ```
//@
//@ Executes the given `command` _synchronously_, unless otherwise specified.
//@ When in synchronous mode returns the object `{ code:..., output:... }`, containing the program's
//@ `output` (stdout + stderr) and its exit `code`. Otherwise the `callback` gets the
//@ arguments `(code, output)`.
function _exec(command, options, callback) {
if (!command)
error('must specify command');
if (typeof options === 'function') {
callback = options;
options = {};
}
options = extend({
silent: false,
async: false
}, options);
if (options.async)
execAsync(command, options, callback);
else
return execSync(command, options);
};
exports.exec = wrap('exec', _exec, {notUnix:true});
//@
//@ ## Non-Unix commands
//@
//@
//@ #### tempdir()
//@ Searches and returns string containing a writeable, platform-dependent temporary directory.
//@ Follows Python's [tempfile algorithm](http://docs.python.org/library/tempfile.html#tempfile.tempdir).
exports.tempdir = wrap('tempdir', tempDir);
//@
//@ #### exists(path [, path ...])
//@ #### exists(path_array)
//@ Returns true if all the given paths exist.
function _exists(options, paths) {
if (!paths)
error('no paths given');
if (typeof paths === 'string')
paths = [].slice.call(arguments, 1);
// if it's array leave it as it is
var exists = true;
paths.forEach(function(p) {
if (!fs.existsSync(p))
exists = false;
});
return exists;
};
exports.exists = wrap('exists', _exists);
//@
//@ #### error()
//@ Tests if error occurred in the last command. Returns `null` if no error occurred,
//@ otherwise returns string explaining the error
exports.error = function() {
return state.error;
}
//@
//@ #### verbose()
//@ Enables all output (default)
exports.verbose = function() {
state.silent = false;
}
//@
//@ #### silent()
//@ Suppresses all output, except for explict `echo()` calls
exports.silent = function() {
state.silent = true;
}
////////////////////////////////////////////////////////////////////////////////////////////////
//
// Auxiliary functions (internal use only)
//
function log() {
if (!state.silent)
console.log.apply(this, arguments);
}
function write(msg) {
if (!state.silent)
process.stdout.write(msg);
}
// Shows error message. Throws unless '_continue = true'.
function error(msg, _continue) {
if (state.error === null)
state.error = '';
state.error += state.currentCmd + ': ' + msg + '\n';
log(state.error);
if (!_continue)
throw '';
}
// Returns {'alice': true, 'bob': false} when passed:
// parseOptions('-a', {'a':'alice', 'b':'bob'});
function parseOptions(str, map) {
if (!map)
error('parseOptions() internal error: no map given');
// All options are false by default
var options = {};
for (letter in map)
options[map[letter]] = false;
if (!str)
return options; // defaults
if (typeof str !== 'string')
error('parseOptions() internal error: wrong str');
// e.g. match[1] = 'Rf' for str = '-Rf'
var match = str.match(/^\-(.+)/);
if (!match)
return options;
// e.g. chars = ['R', 'f']
var chars = match[1].split('');
chars.forEach(function(char) {
if (char in map)
options[map[char]] = true;
else
error('option not recognized: '+char);
});
return options;
}
// Common wrapper for all Unix-like commands
function wrap(cmd, fn, options) {
return function() {
var retValue = null;
state.currentCmd = cmd;
state.error = null;
try {
var args = [].slice.call(arguments, 0);
if (options && options.notUnix) {
retValue = fn.apply(this, args);
} else {
if (args.length === 0 || typeof args[0] !== 'string' || args[0][0] !== '-')
args.unshift(''); // only add dummy option if '-option' not already present
retValue = fn.apply(this, args);
}
} catch (e) {
if (!state.error) {
// If state.error hasn't been set it's an error thrown by Node, not us - probably a bug...
console.log('maker.js: internal error');
console.log(e.stack || e);
process.exit(1);
}
if (state.fatal)
throw e;
}
state.currentCmd = 'maker.js';
return retValue;
}
} // wrap
// Buffered file copy, synchronous
// (Using readFileSync() + writeFileSync() could easily cause a memory overflow
// with large files)
function copyFileSync(srcFile, destFile) {
if (!fs.existsSync(srcFile))
error('copyFileSync: no such file or directory: ' + srcFile);
var BUF_LENGTH = 64*1024,
buf = new Buffer(BUF_LENGTH),
fdr = fs.openSync(srcFile, 'r'),
fdw = fs.openSync(destFile, 'w'),
bytesRead = BUF_LENGTH,
pos = 0;
while (bytesRead === BUF_LENGTH) {
bytesRead = fs.readSync(fdr, buf, 0, BUF_LENGTH, pos);
fs.writeSync(fdw, buf, 0, bytesRead);
pos += bytesRead;
}
fs.closeSync(fdr);
fs.closeSync(fdw);
}
// Recursively copies 'sourceDir' into 'destDir'
// Adapted from https://github.com/ryanmcgrath/wrench-js
//
// Copyright (c) 2010 Ryan McGrath
// Copyright (c) 2012 Artur Adib
//
// Licensed under the MIT License
// http://www.opensource.org/licenses/mit-license.php
function cpdirSyncRecursive(sourceDir, destDir, opts) {
if (!opts) opts = {};
/* Create the directory where all our junk is moving to; read the mode of the source directory and mirror it */
var checkDir = fs.statSync(sourceDir);
try {
fs.mkdirSync(destDir, checkDir.mode);
} catch (e) {
//if the directory already exists, that's okay
if (e.code !== 'EEXIST') throw e;
}
var files = fs.readdirSync(sourceDir);
for(var i = 0; i < files.length; i++) {
var currFile = fs.lstatSync(sourceDir + "/" + files[i]);
if (currFile.isDirectory()) {
/* recursion this thing right on back. */
cpdirSyncRecursive(sourceDir + "/" + files[i], destDir + "/" + files[i], opts);
} else if (currFile.isSymbolicLink()) {
var symlinkFull = fs.readlinkSync(sourceDir + "/" + files[i]);
fs.symlinkSync(symlinkFull, destDir + "/" + files[i]);
} else {
/* At this point, we've hit a file actually worth copying... so copy it on over. */
if (fs.existsSync(destDir + "/" + files[i]) && !opts.force) {
log('skipping existing file: ' + files[i]);
} else {
copyFileSync(sourceDir + "/" + files[i], destDir + "/" + files[i]);
}
}
} // for files
}; // cpdirSyncRecursive
// Recursively removes 'dir'
// Adapted from https://github.com/ryanmcgrath/wrench-js
//
// Copyright (c) 2010 Ryan McGrath
// Copyright (c) 2012 Artur Adib
//
// Licensed under the MIT License
// http://www.opensource.org/licenses/mit-license.php
function rmdirSyncRecursive(dir) {
var files;
files = fs.readdirSync(dir);
// Loop through and delete everything in the sub-tree after checking it
for(var i = 0; i < files.length; i++) {
var currFile = fs.lstatSync(dir + "/" + files[i]);
if(currFile.isDirectory()) // Recursive function back to the beginning
rmdirSyncRecursive(dir + "/" + files[i]);
else if(currFile.isSymbolicLink()) // Unlink symlinks
fs.unlinkSync(dir + "/" + files[i]);
else // Assume it's a file - perhaps a try/catch belongs here?
fs.unlinkSync(dir + "/" + files[i]);
}
// Now that we know everything in the sub-tree has been deleted, we can delete the main directory.
// Huzzah for the shopkeep.
return fs.rmdirSync(dir);
}; // rmdirSyncRecursive
// Recursively creates 'dir'
function mkdirSyncRecursive(dir) {
var baseDir = path.dirname(dir);
// Base dir exists, no recursion necessary
if (fs.existsSync(baseDir)) {
fs.mkdirSync(dir, 0777);
return;
}
// Base dir does not exist, go recursive
mkdirSyncRecursive(baseDir);
// Base dir created, can create dir
fs.mkdirSync(dir, 0777);
};
// e.g. 'makerjs_a5f185d0443ca...'
function randomFileName() {
function randomHash(count) {
if (count === 1)
return parseInt(16*Math.random()).toString(16);
else {
var hash = '';
for (var i=0; i<count; i++)
hash += randomHash(1);
return hash;
}
}
return 'makerjs_'+randomHash(20);
}
// Returns false if 'dir' is not a writeable directory, 'dir' otherwise
function writeableDir(dir) {
if (!dir || !fs.existsSync(dir))
return false;
if (!fs.statSync(dir).isDirectory())
return false;
var testFile = dir+'/'+randomFileName();
try {
fs.writeFileSync(testFile, ' ');
fs.unlinkSync(testFile);
return dir;
} catch (e) {
return false;
}
}
// Cross-platform method for getting an available temporary directory.
// Follows the algorithm of Python's tempfile.tempdir
// http://docs.python.org/library/tempfile.html#tempfile.tempdir
function tempDir() {
if (state.tempDir)
return state.tempDir; // from cache
state.tempDir = writeableDir(process.env['TMPDIR']) ||
writeableDir(process.env['TEMP']) ||
writeableDir(process.env['TMP']) ||
writeableDir(process.env['Wimp$ScrapDir']) || // RiscOS
writeableDir('C:\\TEMP') || // Windows
writeableDir('C:\\TMP') || // Windows
writeableDir('\\TEMP') || // Windows
writeableDir('\\TMP') || // Windows
writeableDir('/tmp') ||
writeableDir('/var/tmp') ||
writeableDir('/usr/tmp') ||
writeableDir('.'); // last resort
return state.tempDir;
}
// Wrapper around exec() to enable echoing output to console in real time
function execAsync(cmd, opts, callback) {
var output = '';
var c = child.exec(cmd, {env: process.env}, function(err) {
if (callback)
callback(err ? err.code : 0, output);
});
c.stdout.on('data', function(data) {
output += data;
if (!opts.silent)
write(data);
});
c.stderr.on('data', function(data) {
output += data;
if (!opts.silent)
write(data);
});
}
// Hack to run child_process.exec() synchronously (sync avoids callback hell)
// Uses a custom wait loop that checks for a flag file, created when the child process is done.
// (Can't do a wait loop that checks for internal Node variables/messages as
// Node is single-threaded; callbacks and other internal state changes are done in the
// event loop).
function execSync(cmd, opts) {
var stdoutFile = path.resolve(tempDir()+'/'+randomFileName()),
codeFile = path.resolve(tempDir()+'/'+randomFileName()),
scriptFile = path.resolve(tempDir()+'/'+randomFileName());
var options = extend({
silent: false
}, opts);
var previousStdoutContent = '';
// Echoes stdout changes from running process, if not silent
function updateStdout() {
if (state.silent || options.silent || !fs.existsSync(stdoutFile))
return;
var stdoutContent = fs.readFileSync(stdoutFile, 'utf8');
// No changes since last time?
if (stdoutContent.length <= previousStdoutContent.length)
return;
process.stdout.write(stdoutContent.substr(previousStdoutContent.length));
previousStdoutContent = stdoutContent;
}
function escape(str) {
str = str.replace(/\'/g, '"');
str = str.replace(/\\/g, '\\\\');
return str;
}
cmd += ' > '+stdoutFile+' 2>&1'; // works on both win/unix
var script =
"var child = require('child_process'), \
fs = require('fs'); \
child.exec('"+escape(cmd)+"', {env: process.env}, function(err) { \
fs.writeFileSync('"+escape(codeFile)+"', err ? err.code.toString() : '0'); \
});";
if (fs.existsSync(scriptFile)) fs.unlinkSync(scriptFile);
if (fs.existsSync(stdoutFile)) fs.unlinkSync(stdoutFile);
if (fs.existsSync(codeFile)) fs.unlinkSync(codeFile);
fs.writeFileSync(scriptFile, script);
child.exec('node '+scriptFile, {
env: process.env,
cwd: exports.pwd()
});
// The wait loop
while (!fs.existsSync(codeFile)) { updateStdout(); };
while (!fs.existsSync(stdoutFile)) { updateStdout(); };
// At this point codeFile exists, but it's not necessarily flushed yet.
// Keep reading it until it is.
var code = parseInt('');
while (isNaN(code))
code = parseInt(fs.readFileSync(codeFile, 'utf8'));
var stdout = fs.readFileSync(stdoutFile, 'utf8');
fs.unlinkSync(scriptFile);
fs.unlinkSync(stdoutFile);
fs.unlinkSync(codeFile);
// True if successful, false if not
var obj = {
code: code,
output: stdout
};
return obj;
} // execSync()
// Expands wildcards with matching file names. For a given array of file names 'list', returns
// another array containing all file names as per ls(list[i]).
// For example: expand(['file*.js']) = ['file1.js', 'file2.js', ...]
// (if the files 'file1.js', 'file2.js', etc, exist in the current dir)
function expand(list) {
var expanded = [];
list.forEach(function(listEl) {
// Wildcard present?
if (listEl.search(/\*/) > -1) {
for (file in _ls('', listEl))
expanded.push(file);
} else {
expanded.push(listEl);
}
});
return expanded;
}
// Cross-platform method for splitting environment PATH variables
function splitPath(p) {
if (!p)
return [];
if (platform === 'win')
return p.split(';');
else
return p.split(':');
}
// extend(target_obj, source_obj1 [, source_obj2 ...])
// Shallow extend, e.g.:
// aux.extend({a:1}, {b:2}, {c:3})
// returns {a:1, b:2, c:3}
function extend(target) {
var sources = [].slice.call(arguments, 1);
sources.forEach(function(source) {
for (key in source)
target[key] = source[key];
});
return target;
}