зеркало из https://github.com/microsoft/shelljs.git
1208 строки
31 KiB
JavaScript
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;
|
|
}
|