UNPKG

d2-ui

Version:
1,735 lines (1,493 loc) 67.6 kB
var common = require('./common'); var fs = require('fs'); //@ //@ ### 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) common.error('no paths given'); if (typeof files === 'string') files = [].slice.call(arguments, 1); // if it's array leave it as it is files = common.expand(files); files.forEach(function(file) { if (!fs.existsSync(file)) common.error('no such file or directory: ' + file); cat += fs.readFileSync(file, 'utf8'); }); return common.ShellString(cat); } module.exports = _cat; var fs = require('fs'); var common = require('./common'); //@ //@ ### cd([dir]) //@ Changes to directory `dir` for the duration of the script. Changes to home //@ directory if no argument is supplied. function _cd(options, dir) { if (!dir) dir = common.getUserHome(); if (dir === '-') { if (!common.state.previousDir) common.error('could not find previous directory'); else dir = common.state.previousDir; } if (!fs.existsSync(dir)) common.error('no such file or directory: ' + dir); if (!fs.statSync(dir).isDirectory()) common.error('not a directory: ' + dir); common.state.previousDir = process.cwd(); process.chdir(dir); } module.exports = _cd; var common = require('./common'); var fs = require('fs'); var path = require('path'); var PERMS = (function (base) { return { OTHER_EXEC : base.EXEC, OTHER_WRITE : base.WRITE, OTHER_READ : base.READ, GROUP_EXEC : base.EXEC << 3, GROUP_WRITE : base.WRITE << 3, GROUP_READ : base.READ << 3, OWNER_EXEC : base.EXEC << 6, OWNER_WRITE : base.WRITE << 6, OWNER_READ : base.READ << 6, // Literal octal numbers are apparently not allowed in "strict" javascript. Using parseInt is // the preferred way, else a jshint warning is thrown. STICKY : parseInt('01000', 8), SETGID : parseInt('02000', 8), SETUID : parseInt('04000', 8), TYPE_MASK : parseInt('0770000', 8) }; })({ EXEC : 1, WRITE : 2, READ : 4 }); //@ //@ ### chmod(octal_mode || octal_string, file) //@ ### chmod(symbolic_mode, file) //@ //@ Available options: //@ //@ + `-v`: output a diagnostic for every file processed//@ //@ + `-c`: like verbose but report only when a change is made//@ //@ + `-R`: change files and directories recursively//@ //@ //@ Examples: //@ //@ ```javascript //@ chmod(755, '/Users/brandon'); //@ chmod('755', '/Users/brandon'); // same as above //@ chmod('u+x', '/Users/brandon'); //@ ``` //@ //@ Alters the permissions of a file or directory by either specifying the //@ absolute permissions in octal form or expressing the changes in symbols. //@ This command tries to mimic the POSIX behavior as much as possible. //@ Notable exceptions: //@ //@ + In symbolic modes, 'a-r' and '-r' are identical. No consideration is //@ given to the umask. //@ + There is no "quiet" option since default behavior is to run silent. function _chmod(options, mode, filePattern) { if (!filePattern) { if (options.length > 0 && options.charAt(0) === '-') { // Special case where the specified file permissions started with - to subtract perms, which // get picked up by the option parser as command flags. // If we are down by one argument and options starts with -, shift everything over. filePattern = mode; mode = options; options = ''; } else { common.error('You must specify a file.'); } } options = common.parseOptions(options, { 'R': 'recursive', 'c': 'changes', 'v': 'verbose' }); if (typeof filePattern === 'string') { filePattern = [ filePattern ]; } var files; if (options.recursive) { files = []; common.expand(filePattern).forEach(function addFile(expandedFile) { var stat = fs.lstatSync(expandedFile); if (!stat.isSymbolicLink()) { files.push(expandedFile); if (stat.isDirectory()) { // intentionally does not follow symlinks. fs.readdirSync(expandedFile).forEach(function (child) { addFile(expandedFile + '/' + child); }); } } }); } else { files = common.expand(filePattern); } files.forEach(function innerChmod(file) { file = path.resolve(file); if (!fs.existsSync(file)) { common.error('File not found: ' + file); } // When recursing, don't follow symlinks. if (options.recursive && fs.lstatSync(file).isSymbolicLink()) { return; } var stat = fs.statSync(file); var isDir = stat.isDirectory(); var perms = stat.mode; var type = perms & PERMS.TYPE_MASK; var newPerms = perms; if (isNaN(parseInt(mode, 8))) { // parse options mode.split(',').forEach(function (symbolicMode) { /*jshint regexdash:true */ var pattern = /([ugoa]*)([=\+-])([rwxXst]*)/i; var matches = pattern.exec(symbolicMode); if (matches) { var applyTo = matches[1]; var operator = matches[2]; var change = matches[3]; var changeOwner = applyTo.indexOf('u') != -1 || applyTo === 'a' || applyTo === ''; var changeGroup = applyTo.indexOf('g') != -1 || applyTo === 'a' || applyTo === ''; var changeOther = applyTo.indexOf('o') != -1 || applyTo === 'a' || applyTo === ''; var changeRead = change.indexOf('r') != -1; var changeWrite = change.indexOf('w') != -1; var changeExec = change.indexOf('x') != -1; var changeExecDir = change.indexOf('X') != -1; var changeSticky = change.indexOf('t') != -1; var changeSetuid = change.indexOf('s') != -1; if (changeExecDir && isDir) changeExec = true; var mask = 0; if (changeOwner) { mask |= (changeRead ? PERMS.OWNER_READ : 0) + (changeWrite ? PERMS.OWNER_WRITE : 0) + (changeExec ? PERMS.OWNER_EXEC : 0) + (changeSetuid ? PERMS.SETUID : 0); } if (changeGroup) { mask |= (changeRead ? PERMS.GROUP_READ : 0) + (changeWrite ? PERMS.GROUP_WRITE : 0) + (changeExec ? PERMS.GROUP_EXEC : 0) + (changeSetuid ? PERMS.SETGID : 0); } if (changeOther) { mask |= (changeRead ? PERMS.OTHER_READ : 0) + (changeWrite ? PERMS.OTHER_WRITE : 0) + (changeExec ? PERMS.OTHER_EXEC : 0); } // Sticky bit is special - it's not tied to user, group or other. if (changeSticky) { mask |= PERMS.STICKY; } switch (operator) { case '+': newPerms |= mask; break; case '-': newPerms &= ~mask; break; case '=': newPerms = type + mask; // According to POSIX, when using = to explicitly set the permissions, setuid and setgid can never be cleared. if (fs.statSync(file).isDirectory()) { newPerms |= (PERMS.SETUID + PERMS.SETGID) & perms; } break; } if (options.verbose) { console.log(file + ' -> ' + newPerms.toString(8)); } if (perms != newPerms) { if (!options.verbose && options.changes) { console.log(file + ' -> ' + newPerms.toString(8)); } fs.chmodSync(file, newPerms); perms = newPerms; // for the next round of changes! } } else { common.error('Invalid symbolic mode change: ' + symbolicMode); } }); } else { // they gave us a full number newPerms = type + parseInt(mode, 8); // POSIX rules are that setuid and setgid can only be added using numeric form, but not cleared. if (fs.statSync(file).isDirectory()) { newPerms |= (PERMS.SETUID + PERMS.SETGID) & perms; } fs.chmodSync(file, newPerms); } }); } module.exports = _chmod; var os = require('os'); var fs = require('fs'); var _ls = require('./ls'); // Module globals var config = { silent: false, fatal: false, verbose: false, }; exports.config = config; var state = { error: null, currentCmd: 'shell.js', previousDir: null, tempDir: null }; exports.state = state; var platform = os.type().match(/^Win/) ? 'win' : 'unix'; exports.platform = platform; function log() { if (!config.silent) console.error.apply(console, arguments); } exports.log = log; // Shows error message. Throws unless _continue or config.fatal are true function error(msg, _continue) { if (state.error === null) state.error = ''; var log_entry = state.currentCmd + ': ' + msg; if (state.error === '') state.error = log_entry; else state.error += '\n' + log_entry; if (msg.length > 0) log(log_entry); if (config.fatal) process.exit(1); if (!_continue) throw ''; } exports.error = error; // 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; } exports.ShellString = ShellString; // Return the home directory in a platform-agnostic way, with consideration for // older versions of node function getUserHome() { var result; if (os.homedir) result = os.homedir(); // node 3+ else result = process.env[(process.platform === 'win32') ? 'USERPROFILE' : 'HOME']; return result; } exports.getUserHome = getUserHome; // Returns {'alice': true, 'bob': false} when passed a dictionary, e.g.: // 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 (var letter in map) { if (!map[letter].match('^!')) 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(''); var opt; chars.forEach(function(c) { if (c in map) { opt = map[c]; if (opt.match('^!')) options[opt.slice(1, opt.length-1)] = false; else options[opt] = true; } else { error('option not recognized: '+c); } }); return options; } exports.parseOptions = parseOptions; // Expands wildcards with matching (ie. existing) file names. // 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 on directory names ? if(listEl.search(/\*[^\/]*\//) > -1 || listEl.search(/\*\*[^\/]*\//) > -1) { var match = listEl.match(/^([^*]+\/|)(.*)/); var root = match[1]; var rest = match[2]; var restRegex = rest.replace(/\*\*/g, ".*").replace(/\*/g, "[^\\/]*"); restRegex = new RegExp(restRegex); _ls('-R', root).filter(function (e) { return restRegex.test(e); }).forEach(function(file) { expanded.push(file); }); } // Wildcard present on file names ? else if (listEl.search(/\*/) > -1) { _ls('', listEl).forEach(function(file) { expanded.push(file); }); } else { expanded.push(listEl); } }); return expanded; } exports.expand = expand; // Normalizes _unlinkSync() across platforms to match Unix behavior, i.e. // file can be unlinked even if it's read-only, see https://github.com/joyent/node/issues/3006 function unlinkSync(file) { try { fs.unlinkSync(file); } catch(e) { // Try to override file permission if (e.code === 'EPERM') { fs.chmodSync(file, '0666'); fs.unlinkSync(file); } else { throw e; } } } exports.unlinkSync = unlinkSync; // e.g. 'shelljs_a5f185d0443ca...' function randomFileName() { function randomHash(count) { if (count === 1) return parseInt(16*Math.random(), 10).toString(16); else { var hash = ''; for (var i=0; i<count; i++) hash += randomHash(1); return hash; } } return 'shelljs_'+randomHash(20); } exports.randomFileName = randomFileName; // extend(target_obj, source_obj1 [, source_obj2 ...]) // Shallow extend, e.g.: // 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 (var key in source) target[key] = source[key]; }); return target; } exports.extend = extend; // 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 (config.verbose) { args.unshift(cmd); console.log.apply(console, args); args.shift(); } if (options && options.notUnix) { retValue = fn.apply(this, args); } else { if (args.length === 0 || typeof args[0] !== 'string' || args[0].length <= 1 || args[0][0] !== '-') args.unshift(''); // only add dummy option if '-option' not already present // Expand the '~' if appropriate var homeDir = getUserHome(); args = args.map(function(arg) { if (typeof arg === 'string' && arg.slice(0, 2) === '~/' || arg === '~') return arg.replace(/^~/, homeDir); else return arg; }); 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('shell.js: internal error'); console.log(e.stack || e); process.exit(1); } if (config.fatal) throw e; } state.currentCmd = 'shell.js'; return retValue; }; } // wrap exports.wrap = wrap; var fs = require('fs'); var path = require('path'); var common = require('./common'); var os = require('os'); // Buffered file copy, synchronous // (Using readFileSync() + writeFileSync() could easily cause a memory overflow // with large files) function copyFileSync(srcFile, destFile) { if (!fs.existsSync(srcFile)) common.error('copyFileSync: no such file or directory: ' + srcFile); var BUF_LENGTH = 64*1024, buf = new Buffer(BUF_LENGTH), bytesRead = BUF_LENGTH, pos = 0, fdr = null, fdw = null; try { fdr = fs.openSync(srcFile, 'r'); } catch(e) { common.error('copyFileSync: could not read src file ('+srcFile+')'); } try { fdw = fs.openSync(destFile, 'w'); } catch(e) { common.error('copyFileSync: could not write to dest file (code='+e.code+'):'+destFile); } 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); fs.chmodSync(destFile, fs.statSync(srcFile).mode); } // 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 srcFile = sourceDir + "/" + files[i]; var destFile = destDir + "/" + files[i]; var srcFileStat = fs.lstatSync(srcFile); if (srcFileStat.isDirectory()) { /* recursion this thing right on back. */ cpdirSyncRecursive(srcFile, destFile, opts); } else if (srcFileStat.isSymbolicLink()) { var symlinkFull = fs.readlinkSync(srcFile); fs.symlinkSync(symlinkFull, destFile, os.platform() === "win32" ? "junction" : null); } else { /* At this point, we've hit a file actually worth copying... so copy it on over. */ if (fs.existsSync(destFile) && opts.no_force) { common.log('skipping existing file: ' + files[i]); } else { copyFileSync(srcFile, destFile); } } } // for files } // cpdirSyncRecursive //@ //@ ### cp([options,] source [, source ...], dest) //@ ### cp([options,] source_array, dest) //@ Available options: //@ //@ + `-f`: force (default behavior) //@ + `-n`: no-clobber //@ + `-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 = common.parseOptions(options, { 'f': '!no_force', 'n': 'no_force', 'R': 'recursive', 'r': 'recursive' }); // Get sources, dest if (arguments.length < 3) { common.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 { common.error('invalid arguments'); } var exists = fs.existsSync(dest), stats = exists && fs.statSync(dest); // Dest is not existing dir, but multiple sources given if ((!exists || !stats.isDirectory()) && sources.length > 1) common.error('dest is not a directory (too many sources)'); // Dest is an existing file, but no -f given if (exists && stats.isFile() && options.no_force) common.error('dest file already exists: ' + dest); if (options.recursive) { // Recursive allows the shortcut syntax "sourcedir/" for "sourcedir/*" // (see Github issue #15) sources.forEach(function(src, i) { if (src[src.length - 1] === '/') { sources[i] += '*'; // If src is a directory and dest doesn't exist, 'cp -r src dest' should copy src/* into dest } else if (fs.statSync(src).isDirectory() && !exists) { sources[i] += '/*'; } }); // Create dest try { fs.mkdirSync(dest, parseInt('0777', 8)); } catch (e) { // like Unix's cp, keep going even if we can't create dest dir } } sources = common.expand(sources); sources.forEach(function(src) { if (!fs.existsSync(src)) { common.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 common.log(src + ' is a directory (not copied)'); } else { // Recursive // 'cp /a/source dest' should create 'source' in 'dest' var newDest = path.join(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') { common.error('dest file no such file or directory: ' + newDest, true); throw e; } } cpdirSyncRecursive(src, newDest, {no_force: options.no_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.no_force) { common.error('dest file already exists: ' + thisDest, true); return; // skip file } copyFileSync(src, thisDest); }); // forEach(src) } module.exports = _cp; var common = require('./common'); var _cd = require('./cd'); var path = require('path'); // Pushd/popd/dirs internals var _dirStack = []; function _isStackIndex(index) { return (/^[\-+]\d+$/).test(index); } function _parseStackIndex(index) { if (_isStackIndex(index)) { if (Math.abs(index) < _dirStack.length + 1) { // +1 for pwd return (/^-/).test(index) ? Number(index) - 1 : Number(index); } else { common.error(index + ': directory stack index out of range'); } } else { common.error(index + ': invalid number'); } } function _actualDirStack() { return [process.cwd()].concat(_dirStack); } //@ //@ ### pushd([options,] [dir | '-N' | '+N']) //@ //@ Available options: //@ //@ + `-n`: Suppresses the normal change of directory when adding directories to the stack, so that only the stack is manipulated. //@ //@ Arguments: //@ //@ + `dir`: Makes the current working directory be the top of the stack, and then executes the equivalent of `cd dir`. //@ + `+N`: Brings the Nth directory (counting from the left of the list printed by dirs, starting with zero) to the top of the list by rotating the stack. //@ + `-N`: Brings the Nth directory (counting from the right of the list printed by dirs, starting with zero) to the top of the list by rotating the stack. //@ //@ Examples: //@ //@ ```javascript //@ // process.cwd() === '/usr' //@ pushd('/etc'); // Returns /etc /usr //@ pushd('+1'); // Returns /usr /etc //@ ``` //@ //@ Save the current directory on the top of the directory stack and then cd to `dir`. With no arguments, pushd exchanges the top two directories. Returns an array of paths in the stack. function _pushd(options, dir) { if (_isStackIndex(options)) { dir = options; options = ''; } options = common.parseOptions(options, { 'n' : 'no-cd' }); var dirs = _actualDirStack(); if (dir === '+0') { return dirs; // +0 is a noop } else if (!dir) { if (dirs.length > 1) { dirs = dirs.splice(1, 1).concat(dirs); } else { return common.error('no other directory'); } } else if (_isStackIndex(dir)) { var n = _parseStackIndex(dir); dirs = dirs.slice(n).concat(dirs.slice(0, n)); } else { if (options['no-cd']) { dirs.splice(1, 0, dir); } else { dirs.unshift(dir); } } if (options['no-cd']) { dirs = dirs.slice(1); } else { dir = path.resolve(dirs.shift()); _cd('', dir); } _dirStack = dirs; return _dirs(''); } exports.pushd = _pushd; //@ //@ ### popd([options,] ['-N' | '+N']) //@ //@ Available options: //@ //@ + `-n`: Suppresses the normal change of directory when removing directories from the stack, so that only the stack is manipulated. //@ //@ Arguments: //@ //@ + `+N`: Removes the Nth directory (counting from the left of the list printed by dirs), starting with zero. //@ + `-N`: Removes the Nth directory (counting from the right of the list printed by dirs), starting with zero. //@ //@ Examples: //@ //@ ```javascript //@ echo(process.cwd()); // '/usr' //@ pushd('/etc'); // '/etc /usr' //@ echo(process.cwd()); // '/etc' //@ popd(); // '/usr' //@ echo(process.cwd()); // '/usr' //@ ``` //@ //@ When no arguments are given, popd removes the top directory from the stack and performs a cd to the new top directory. The elements are numbered from 0 starting at the first directory listed with dirs; i.e., popd is equivalent to popd +0. Returns an array of paths in the stack. function _popd(options, index) { if (_isStackIndex(options)) { index = options; options = ''; } options = common.parseOptions(options, { 'n' : 'no-cd' }); if (!_dirStack.length) { return common.error('directory stack empty'); } index = _parseStackIndex(index || '+0'); if (options['no-cd'] || index > 0 || _dirStack.length + index === 0) { index = index > 0 ? index - 1 : index; _dirStack.splice(index, 1); } else { var dir = path.resolve(_dirStack.shift()); _cd('', dir); } return _dirs(''); } exports.popd = _popd; //@ //@ ### dirs([options | '+N' | '-N']) //@ //@ Available options: //@ //@ + `-c`: Clears the directory stack by deleting all of the elements. //@ //@ Arguments: //@ //@ + `+N`: Displays the Nth directory (counting from the left of the list printed by dirs when invoked without options), starting with zero. //@ + `-N`: Displays the Nth directory (counting from the right of the list printed by dirs when invoked without options), starting with zero. //@ //@ Display the list of currently remembered directories. Returns an array of paths in the stack, or a single path if +N or -N was specified. //@ //@ See also: pushd, popd function _dirs(options, index) { if (_isStackIndex(options)) { index = options; options = ''; } options = common.parseOptions(options, { 'c' : 'clear' }); if (options['clear']) { _dirStack = []; return _dirStack; } var stack = _actualDirStack(); if (index) { index = _parseStackIndex(index); if (index < 0) { index = stack.length + index; } common.log(stack[index]); return stack[index]; } common.log(stack.join(' ')); return stack; } exports.dirs = _dirs; var common = require('./common'); //@ //@ ### 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() { var messages = [].slice.call(arguments, 0); console.log.apply(console, messages); return common.ShellString(messages.join(' ')); } module.exports = _echo; var common = require('./common'); //@ //@ ### error() //@ Tests if error occurred in the last command. Returns `null` if no error occurred, //@ otherwise returns string explaining the error function error() { return common.state.error; } module.exports = error; var common = require('./common'); var _tempDir = require('./tempdir'); var _pwd = require('./pwd'); var path = require('path'); var fs = require('fs'); var child = require('child_process'); // 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 tempDir = _tempDir(); var stdoutFile = path.resolve(tempDir+'/'+common.randomFileName()), stderrFile = path.resolve(tempDir+'/'+common.randomFileName()), codeFile = path.resolve(tempDir+'/'+common.randomFileName()), scriptFile = path.resolve(tempDir+'/'+common.randomFileName()), sleepFile = path.resolve(tempDir+'/'+common.randomFileName()); var options = common.extend({ silent: common.config.silent }, opts); var previousStdoutContent = '', previousStderrContent = ''; // Echoes stdout and stderr changes from running process, if not silent function updateStream(streamFile) { if (options.silent || !fs.existsSync(streamFile)) return; var previousStreamContent, proc_stream; if (streamFile === stdoutFile) { previousStreamContent = previousStdoutContent; proc_stream = process.stdout; } else { // assume stderr previousStreamContent = previousStderrContent; proc_stream = process.stderr; } var streamContent = fs.readFileSync(streamFile, 'utf8'); // No changes since last time? if (streamContent.length <= previousStreamContent.length) return; proc_stream.write(streamContent.substr(previousStreamContent.length)); previousStreamContent = streamContent; } function escape(str) { return (str+'').replace(/([\\"'])/g, "\\$1").replace(/\0/g, "\\0"); } if (fs.existsSync(scriptFile)) common.unlinkSync(scriptFile); if (fs.existsSync(stdoutFile)) common.unlinkSync(stdoutFile); if (fs.existsSync(stderrFile)) common.unlinkSync(stderrFile); if (fs.existsSync(codeFile)) common.unlinkSync(codeFile); var execCommand = '"'+process.execPath+'" '+scriptFile; var execOptions = { env: process.env, cwd: _pwd(), maxBuffer: 20*1024*1024 }; var script; if (typeof child.execSync === 'function') { script = [ "var child = require('child_process')", " , fs = require('fs');", "var childProcess = child.exec('"+escape(cmd)+"', {env: process.env, maxBuffer: 20*1024*1024}, function(err) {", " fs.writeFileSync('"+escape(codeFile)+"', err ? err.code.toString() : '0');", "});", "var stdoutStream = fs.createWriteStream('"+escape(stdoutFile)+"');", "var stderrStream = fs.createWriteStream('"+escape(stderrFile)+"');", "childProcess.stdout.pipe(stdoutStream, {end: false});", "childProcess.stderr.pipe(stderrStream, {end: false});", "childProcess.stdout.pipe(process.stdout);", "childProcess.stderr.pipe(process.stderr);", "var stdoutEnded = false, stderrEnded = false;", "function tryClosingStdout(){ if(stdoutEnded){ stdoutStream.end(); } }", "function tryClosingStderr(){ if(stderrEnded){ stderrStream.end(); } }", "childProcess.stdout.on('end', function(){ stdoutEnded = true; tryClosingStdout(); });", "childProcess.stderr.on('end', function(){ stderrEnded = true; tryClosingStderr(); });" ].join('\n'); fs.writeFileSync(scriptFile, script); if (options.silent) { execOptions.stdio = 'ignore'; } else { execOptions.stdio = [0, 1, 2]; } // Welcome to the future child.execSync(execCommand, execOptions); } else { cmd += ' > '+stdoutFile+' 2> '+stderrFile; // works on both win/unix script = [ "var child = require('child_process')", " , fs = require('fs');", "var childProcess = child.exec('"+escape(cmd)+"', {env: process.env, maxBuffer: 20*1024*1024}, function(err) {", " fs.writeFileSync('"+escape(codeFile)+"', err ? err.code.toString() : '0');", "});" ].join('\n'); fs.writeFileSync(scriptFile, script); child.exec(execCommand, execOptions); // The wait loop // sleepFile is used as a dummy I/O op to mitigate unnecessary CPU usage // (tried many I/O sync ops, writeFileSync() seems to be only one that is effective in reducing // CPU usage, though apparently not so much on Windows) while (!fs.existsSync(codeFile)) { updateStream(stdoutFile); fs.writeFileSync(sleepFile, 'a'); } while (!fs.existsSync(stdoutFile)) { updateStream(stdoutFile); fs.writeFileSync(sleepFile, 'a'); } while (!fs.existsSync(stderrFile)) { updateStream(stderrFile); fs.writeFileSync(sleepFile, 'a'); } } // At this point codeFile exists, but it's not necessarily flushed yet. // Keep reading it until it is. var code = parseInt('', 10); while (isNaN(code)) { code = parseInt(fs.readFileSync(codeFile, 'utf8'), 10); } var stdout = fs.readFileSync(stdoutFile, 'utf8'); var stderr = fs.readFileSync(stderrFile, 'utf8'); // No biggie if we can't erase the files now -- they're in a temp dir anyway try { common.unlinkSync(scriptFile); } catch(e) {} try { common.unlinkSync(stdoutFile); } catch(e) {} try { common.unlinkSync(stderrFile); } catch(e) {} try { common.unlinkSync(codeFile); } catch(e) {} try { common.unlinkSync(sleepFile); } catch(e) {} // some shell return codes are defined as errors, per http://tldp.org/LDP/abs/html/exitcodes.html if (code === 1 || code === 2 || code >= 126) { common.error('', true); // unix/shell doesn't really give an error message after non-zero exit codes } // True if successful, false if not var obj = { code: code, output: stdout, // deprecated stdout: stdout, stderr: stderr }; return obj; } // execSync() // Wrapper around exec() to enable echoing output to console in real time function execAsync(cmd, opts, callback) { var stdout = ''; var stderr = ''; var options = common.extend({ silent: common.config.silent }, opts); var c = child.exec(cmd, {env: process.env, maxBuffer: 20*1024*1024}, function(err) { if (callback) callback(err ? err.code : 0, stdout, stderr); }); c.stdout.on('data', function(data) { stdout += data; if (!options.silent) process.stdout.write(data); }); c.stderr.on('data', function(data) { stderr += data; if (!options.silent) process.stderr.write(data); }); return c; } //@ //@ ### exec(command [, options] [, callback]) //@ Available options (all `false` by default): //@ //@ + `async`: Asynchronous execution. If a callback is provided, it will be set to //@ `true`, regardless of the passed value. //@ + `silent`: Do not echo program output to console. //@ //@ Examples: //@ //@ ```javascript //@ var version = exec('node --version', {silent:true}).stdout; //@ //@ var child = exec('some_long_running_process', {async:true}); //@ child.stdout.on('data', function(data) { //@ /* ... do something with data ... */ //@ }); //@ //@ exec('some_long_running_process', function(code, stdout, stderr) { //@ console.log('Exit code:', code); //@ console.log('Program output:', stdout); //@ console.log('Program stderr:', stderr); //@ }); //@ ``` //@ //@ Executes the given `command` _synchronously_, unless otherwise specified. When in synchronous //@ mode returns the object `{ code:..., stdout:... , stderr:... }`, containing the program's //@ `stdout`, `stderr`, and its exit `code`. Otherwise returns the child process object, //@ and the `callback` gets the arguments `(code, stdout, stderr)`. //@ //@ **Note:** For long-lived processes, it's best to run `exec()` asynchronously as //@ the current synchronous implementation uses a lot of CPU. This should be getting //@ fixed soon. function _exec(command, options, callback) { if (!command) common.error('must specify command'); // Callback is defined instead of options. if (typeof options === 'function') { callback = options; options = { async: true }; } // Callback is defined with options. if (typeof options === 'object' && typeof callback === 'function') { options.async = true; } options = common.extend({ silent: common.config.silent, async: false }, options); if (options.async) return execAsync(command, options, callback); else return execSync(command, options); } module.exports = _exec; var fs = require('fs'); var common = require('./common'); var _ls = require('./ls'); //@ //@ ### find(path [, path ...]) //@ ### find(path_array) //@ Examples: //@ //@ ```javascript //@ find('src', 'lib'); //@ find(['src', 'lib']); // same as above //@ find('.').filter(function(file) { return file.match(/\.js$/); }); //@ ``` //@ //@ Returns array of all files (however deep) in the given paths. //@ //@ The main difference from `ls('-R', path)` is that the resulting file names //@ include the base directories, e.g. `lib/resources/file1` instead of just `file1`. function _find(options, paths) { if (!paths) common.error('no path specified'); else if (typeof paths === 'object') paths = paths; // assume array else if (typeof paths === 'string') paths = [].slice.call(arguments, 1); var list = []; function pushFile(file) { if (common.platform === 'win') file = file.replace(/\\/g, '/'); list.push(file); } // why not simply do ls('-R', paths)? because the output wouldn't give the base dirs // to get the base dir in the output, we need instead ls('-R', 'dir/*') for every directory paths.forEach(function(file) { pushFile(file); if (fs.statSync(file).isDirectory()) { _ls('-RA', file+'/*').forEach(function(subfile) { pushFile(subfile); }); } }); return list; } module.exports = _find; var common = require('./common'); var fs = require('fs'); //@ //@ ### grep([options,] regex_filter, file [, file ...]) //@ ### grep([options,] regex_filter, file_array) //@ Available options: //@ //@ + `-v`: Inverse the sense of the regex and print the lines not matching the criteria. //@ //@ Examples: //@ //@ ```javascript //@ grep('-v', 'GLOBAL_VARIABLE', '*.js'); //@ 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) { options = common.parseOptions(options, { 'v': 'inverse' }); if (!files) common.error('no paths given'); if (typeof files === 'string') files = [].slice.call(arguments, 2); // if it's array leave it as it is files = common.expand(files); var grep = ''; files.forEach(function(file) { if (!fs.existsSync(file)) { common.error('no such file or directory: ' + file, true); return; } var contents = fs.readFileSync(file, 'utf8'), lines = contents.split(/\r*\n/); lines.forEach(function(line) { var matched = line.match(regex); if ((options.inverse && !matched) || (!options.inverse && matched)) grep += line + '\n'; }); }); return common.ShellString(grep); } module.exports = _grep; var fs = require('fs'); var path = require('path'); var common = require('./common'); //@ //@ ### ln([options,] source, dest) //@ Available options: //@ //@ + `-s`: symlink //@ + `-f`: force //@ //@ Examples: //@ //@ ```javascript //@ ln('file', 'newlink'); //@ ln('-sf', 'file', 'existing'); //@ ``` //@ //@ Links source to dest. Use -f to force the link, should dest already exist. function _ln(options, source, dest) { options = common.parseOptions(options, { 's': 'symlink', 'f': 'force' }); if (!source || !dest) { common.error('Missing <source> and/or <dest>'); } source = String(source); var sourcePath = path.normalize(source).replace(RegExp(path.sep + '$'), ''); var isAbsolute = (path.resolve(source) === sourcePath); dest = path.resolve(process.cwd(), String(dest)); if (fs.existsSync(dest)) { if (!options.force) { common.error('Destination file exists', true); } fs.unlinkSync(dest); } if (options.symlink) { var isWindows = common.platform === 'win'; var linkType = isWindows ? 'file' : null; var resolvedSourcePath = isAbsolute ? sourcePath : path.resolve(process.cwd(), path.dirname(dest), source); if (!fs.existsSync(resolvedSourcePath)) { common.error('Source file does not exist', true); } else if (isWindows && fs.statSync(resolvedSourcePath).isDirectory()) { linkType = 'junction'; } try { fs.symlinkSync(linkType === 'junction' ? resolvedSourcePath: source, dest, linkType); } catch (err) { common.error(err.message); } } else { if (!fs.existsSync(source)) { common.error('Source file does not exist', true); } try { fs.linkSync(source, dest); } catch (err) { common.error(err.message); } } } module.exports = _ln; var path = require('path'); var fs = require('fs'); var common = require('./common'); var _cd = require('./cd'); var _pwd = require('./pwd'); //@ //@ ### ls([options,] [path, ...]) //@ ### ls([options,] path_array) //@ Available options: //@ //@ + `-R`: recursive //@ + `-A`: all files (include files beginning with `.`, except for `.` and `..`) //@ + `-d`: list directories themselves, not their contents //@ + `-l`: list objects representing each file, each with fields containing `ls //@ -l` output fields. See //@ [fs.Stats](https://nodejs.org/api/fs.html#fs_class_fs_stats) //@ for more info //@ //@ Examples: //@ //@ ```javascript //@ ls('projs/*.js'); //@ ls('-R', '/users/me', '/tmp'); //@ ls('-R', ['/users/me', '/tmp']); // same as above //@ ls('-l', 'file.txt'); // { name: 'file.txt', mode: 33188, nlink: 1, ...} //@ ``` //@ //@ Returns array of files in the given path, or in current directory if no path provided. function _ls(options, paths) { options = common.parseOptions(options, { 'R': 'recursive', 'A': 'all', 'a': 'all_deprecated', 'd': 'directory', 'l': 'long' }); if (options.all_deprecated) { // We won't support the -a option as it's hard to image why it's useful // (it includes '.' and '..' in addition to '.*' files) // For backwards compatibility we'll dump a deprecated message and proceed as before common.log('ls: Option -a is deprecated. Use -A instead'); options.all = true; } if (!paths) paths = ['.']; else if (typeof paths === 'object') paths = paths; // assume array else if (typeof paths === 'string') paths = [].slice.call(arguments, 1); var list = []; // Conditionally pushes file to list - returns true if pushed, false otherwise // (e.g. prevents hidden files to be included unless explicitly told so) function pushFile(file, query) { var name = file.name || file; // hidden file? if (path.basename(name)[0] === '.') { // not explicitly asking for hidden files? if (!options.all && !(path.basename(query)[0] === '.' && path.basename(query).length > 1)) return false; } if (common.platform === 'win') name = name.replace(/\\/g, '/'); if (file.name) { file.name = name; } else { file = name; } list.push(file); return true; } paths.forEach(function(p) { if (fs.existsSync(p)) { var stats = ls_stat(p); // Simple file? if (stats.isFile()) { if (options.long) { pushFile(stats, p); } else { pushFile(p, p); } return; // continue } // Simple dir? if (options.directory) { pushFile(p, p); return; } else if (stats.isDirectory()) { // Iterate over p contents fs.readdirSync(p).forEach(function(file) { var orig_file = file; if (options.long) file = ls_stat(path.join(p, file)); if (!pushFile(file, p)) return; // Recursive? if (options.recursive) { var oldDir = _pwd(); _cd('', p); if (fs.statSync(orig_file).isDirectory()) list = list.concat(_ls('-R'+(options.all?'A':''), orig_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))) { var file_path = path.join(dirname, file); file_path = options.long ? ls_stat(file_path) : file_path; if (file_path.name) file_path.name = path.normalize(file_path.name); else file_path = path.normalize(file_path); if (!pushFile(file_path, basename)) return; // Recursive? if (options.recursive) { var pp = dirname + '/' + file; if (fs.lstatSync(pp).isDirectory()) list = list.concat(_ls('-R'+(options.all?'A':''), pp+'/*')); } // recursive } // if file matches }); // forEach return; } common.error('no such file or directory: ' + p, true); }); return list; } module.exports = _ls; function ls_stat(path) { var stats = fs.statSync(path); // Note: this object will contain more information than .toString() returns stats.name = path; stats.toString = function() { // Return a string resembling unix's `ls -l` format return [this.mode, this.nlink, this.uid, this.gid, this.size, this.mtime, this.name].join(' '); }; return stats; } var common = require('./common'); var fs = require('fs'); var path = require('path'); // Recursively creates 'dir' function mkdirSyncRecursive(dir) { var baseDir = path.dirname(dir); // Base dir exists, no recursion necessary if (fs.existsSync(baseDir)) { fs.mkdirSync(dir, parseInt('0777', 8)); return; } // Base dir does not exist, go recursive mkdirSyncRecursive(baseDir); // Base dir created, can create dir fs.mkdirSync(dir, parseInt('0777', 8)); } //@ //@ ### 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 = common.parseOptions(options, { 'p': 'fullpath' }); if (!dirs) common.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) common.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) { common.error('no such file or directory: ' + baseDir, true); return; // skip dir } if (options.fullpath) mkdirSyncRecursive(dir); else fs.mkdirSync(dir, parseInt('0777', 8)); }); } // mkdir module.exports = _mkdir; var fs = require('fs'); var path = require('path'); var common = require('./common'); //@ //@ ### mv([options ,] source [, source ...], dest') //@ ### mv([options ,] source_array, dest') //@ Available options: //@ //@ + `-f`: force (default behavior) //@ + `-n`: no-clobber //@ //@ Examples: //@ //@ ```javascript //@ mv('-n', '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 = common.parseOptions(options, { 'f': '!no_force', 'n': 'no_force' }); // Get sources, dest if (arguments.length < 3) { common.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 { common.error('invalid arguments'); } sources = common.expand(sources); var exists = fs.existsSync(dest), stats = exists && fs.statSync(dest); // Dest is not existing dir, but multiple sources given if ((!exists || !stats.isDirectory()) && sources.length > 1) common.error('dest is not a directory (too many sources)'); // Dest is an existing file, but no -f given if (exists && stats.isFile() && options.no_force) common.error('dest file already exists: ' + dest); sources.forEach(function(src) { if (!fs.existsSync(src)) { common.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.no_force) { common.error('dest file already exists: ' + thisDest, true); return; // skip file } if (path.resolve(src) === path.dirname(path.resolve(thisDest))) { common.error('cannot move to self: '+src, true); return; // skip file } fs.renameSync(src, thisDest); }); // forEach(src) } // mv module.exports = _mv; // see dirs.js // see dirs.js var path = require('path'); var common = require('./common'); //@ //@ ### pwd() //@ Returns the current directory. function _pwd() { var pwd = path.resolve(process.cwd()); return common.ShellString(pwd); } module.exports = _pwd; var common = require('./common'); var fs = require('fs'); // 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, force) { var files; files = fs.readdirSync(dir); // Loop through and delete everything in the sub-tree after checking it for(var i = 0