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 and/or '); } 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 and/or '); } 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 and/or '); } 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; i < files.length; i++) { var file = dir + "/" + files[i], currFile = fs.lstatSync(file); if(currFile.isDirectory()) { // Recursive function back to the beginning rmdirSyncRecursive(file, force); } else if(currFile.isSymbolicLink()) { // Unlink symlinks if (force || isWriteable(file)) { try { common.unlinkSync(file); } catch (e) { common.error('could not remove file (code '+e.code+'): ' + file, true); } } } else // Assume it's a file - perhaps a try/catch belongs here? if (force || isWriteable(file)) { try { common.unlinkSync(file); } catch (e) { common.error('could not remove file (code '+e.code+'): ' + file, true); } } } // Now that we know everything in the sub-tree has been deleted, we can delete the main directory. // Huzzah for the shopkeep. var result; try { // Retry on windows, sometimes it takes a little time before all the files in the directory are gone var start = Date.now(); while (true) { try { result = fs.rmdirSync(dir); if (fs.existsSync(dir)) throw { code: "EAGAIN" }; break; } catch(er) { // In addition to error codes, also check if the directory still exists and loop again if true if (process.platform === "win32" && (er.code === "ENOTEMPTY" || er.code === "EBUSY" || er.code === "EPERM" || er.code === "EAGAIN")) { if (Date.now() - start > 1000) throw er; } else if (er.code === "ENOENT") { // Directory did not exist, deletion was successful break; } else { throw er; } } } } catch(e) { common.error('could not remove directory (code '+e.code+'): ' + dir, true); } return result; } // rmdirSyncRecursive // Hack to determine if file has write permissions for current user // Avoids having to check user, group, etc, but it's probably slow function isWriteable(file) { var writePermission = true; try { var __fd = fs.openSync(file, 'a'); fs.closeSync(__fd); } catch(e) { writePermission = false; } return writePermission; } //@ //@ ### 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 = common.parseOptions(options, { 'f': 'force', 'r': 'recursive', 'R': 'recursive' }); 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)) { // Path does not exist, no force flag given if (!options.force) common.error('no such file or directory: '+file, true); return; // skip file } // If here, path exists var stats = fs.lstatSync(file); if (stats.isFile() || stats.isSymbolicLink()) { // Do not check for file writing permissions if (options.force) { common.unlinkSync(file); return; } if (isWriteable(file)) common.unlinkSync(file); else common.error('permission denied: '+file, true); return; } // simple file // Path is an existing directory, but no -r flag given if (stats.isDirectory() && !options.recursive) { common.error('path is a directory', true); return; // skip path } // Recursively remove existing directory if (stats.isDirectory() && options.recursive) { rmdirSyncRecursive(file, options.force); } }); // forEach(file) } // rm module.exports = _rm; var common = require('./common'); var fs = require('fs'); //@ //@ ### sed([options,] search_regex, replacement, file [, file ...]) //@ ### sed([options,] search_regex, replacement, file_array) //@ 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 `files` and performs a JavaScript `replace()` on the input //@ using the given search regex and replacement string or function. Returns the new string after replacement. function _sed(options, regex, replacement, files) { options = common.parseOptions(options, { 'i': 'inplace' }); if (typeof replacement === 'string' || typeof replacement === 'function') replacement = replacement; // no-op else if (typeof replacement === 'number') replacement = replacement.toString(); // fallback else common.error('invalid replacement string'); // Convert all search strings to RegExp if (typeof regex === 'string') regex = RegExp(regex); if (!files) common.error('no files given'); if (typeof files === 'string') files = [].slice.call(arguments, 3); // if it's array leave it as it is files = common.expand(files); var sed = []; files.forEach(function(file) { if (!fs.existsSync(file)) { common.error('no such file or directory: ' + file, true); return; } var result = fs.readFileSync(file, 'utf8').split('\n').map(function (line) { return line.replace(regex, replacement); }).join('\n'); sed.push(result); if (options.inplace) fs.writeFileSync(file, result, 'utf8'); }); return common.ShellString(sed.join('\n')); } module.exports = _sed; var common = require('./common'); //@ //@ ### set(options) //@ Available options: //@ //@ + `+/-e`: exit upon error (`config.fatal`) //@ + `+/-v`: verbose: show all commands (`config.verbose`) //@ //@ Examples: //@ //@ ```javascript //@ set('-e'); // exit upon first error //@ set('+e'); // this undoes a "set('-e')" //@ ``` //@ //@ Sets global configuration variables function _set(options) { if (!options) { var args = [].slice.call(arguments, 0); if (args.length < 2) common.error('must provide an argument'); options = args[1]; } var negate = (options[0] === '+'); if (negate) { options = '-' + options.slice(1); // parseOptions needs a '-' prefix } options = common.parseOptions(options, { 'e': 'fatal', 'v': 'verbose' }); var key; if (negate) { for (key in options) options[key] = !options[key]; } for (key in options) { // Only change the global config if `negate` is false and the option is true // or if `negate` is true and the option is false (aka negate !== option) if (negate !== options[key]) { common.config[key] = options[key]; } } return; } module.exports = _set; var common = require('./common'); var os = require('os'); var fs = require('fs'); // 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+'/'+common.randomFileName(); try { fs.writeFileSync(testFile, ' '); common.unlinkSync(testFile); return dir; } catch (e) { return false; } } //@ //@ ### tempdir() //@ //@ Examples: //@ //@ ```javascript //@ var tmp = tempdir(); // "/tmp" for most *nix platforms //@ ``` //@ //@ 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). function _tempDir() { var state = common.state; if (state.tempDir) return state.tempDir; // from cache state.tempDir = writeableDir(os.tmpdir && os.tmpdir()) || // node 0.10+ writeableDir(os.tmpDir && os.tmpDir()) || // node 0.8+ 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; } module.exports = _tempDir; var common = require('./common'); var fs = require('fs'); //@ //@ ### test(expression) //@ Available expression primaries: //@ //@ + `'-b', 'path'`: true if path is a block device //@ + `'-c', 'path'`: true if path is a character device //@ + `'-d', 'path'`: true if path is a directory //@ + `'-e', 'path'`: true if path exists //@ + `'-f', 'path'`: true if path is a regular file //@ + `'-L', 'path'`: true if path is a symbolic link //@ + `'-p', 'path'`: true if path is a pipe (FIFO) //@ + `'-S', 'path'`: true if path is a socket //@ //@ Examples: //@ //@ ```javascript //@ if (test('-d', path)) { /* do something with dir */ }; //@ if (!test('-f', path)) continue; // skip if it's a regular file //@ ``` //@ //@ Evaluates expression using the available primaries and returns corresponding value. function _test(options, path) { if (!path) common.error('no path given'); // hack - only works with unary primaries options = common.parseOptions(options, { 'b': 'block', 'c': 'character', 'd': 'directory', 'e': 'exists', 'f': 'file', 'L': 'link', 'p': 'pipe', 'S': 'socket' }); var canInterpret = false; for (var key in options) if (options[key] === true) { canInterpret = true; break; } if (!canInterpret) common.error('could not interpret expression'); if (options.link) { try { return fs.lstatSync(path).isSymbolicLink(); } catch(e) { return false; } } if (!fs.existsSync(path)) return false; if (options.exists) return true; var stats = fs.statSync(path); if (options.block) return stats.isBlockDevice(); if (options.character) return stats.isCharacterDevice(); if (options.directory) return stats.isDirectory(); if (options.file) return stats.isFile(); if (options.pipe) return stats.isFIFO(); if (options.socket) return stats.isSocket(); } // test module.exports = _test; var common = require('./common'); var fs = require('fs'); var path = require('path'); //@ //@ ### '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) common.error('wrong arguments'); if (!fs.existsSync( path.dirname(file) )) common.error('no such file or directory: ' + path.dirname(file)); try { fs.writeFileSync(file, this.toString(), 'utf8'); return this; } catch(e) { common.error('could not write to file (code '+e.code+'): '+file, true); } } module.exports = _to; var common = require('./common'); var fs = require('fs'); var path = require('path'); //@ //@ ### 'string'.toEnd(file) //@ //@ Examples: //@ //@ ```javascript //@ cat('input.txt').toEnd('output.txt'); //@ ``` //@ //@ Analogous to the redirect-and-append operator `>>` in Unix, but works with JavaScript strings (such as //@ those returned by `cat`, `grep`, etc). function _toEnd(options, file) { if (!file) common.error('wrong arguments'); if (!fs.existsSync( path.dirname(file) )) common.error('no such file or directory: ' + path.dirname(file)); try { fs.appendFileSync(file, this.toString(), 'utf8'); return this; } catch(e) { common.error('could not append to file (code '+e.code+'): '+file, true); } } module.exports = _toEnd; var common = require('./common'); var fs = require('fs'); //@ //@ ### touch([options,] file) //@ Available options: //@ //@ + `-a`: Change only the access time //@ + `-c`: Do not create any files //@ + `-m`: Change only the modification time //@ + `-d DATE`: Parse DATE and use it instead of current time //@ + `-r FILE`: Use FILE's times instead of current time //@ //@ Examples: //@ //@ ```javascript //@ touch('source.js'); //@ touch('-c', '/path/to/some/dir/source.js'); //@ touch({ '-r': FILE }, '/path/to/some/dir/source.js'); //@ ``` //@ //@ Update the access and modification times of each FILE to the current time. //@ A FILE argument that does not exist is created empty, unless -c is supplied. //@ This is a partial implementation of *[touch(1)](http://linux.die.net/man/1/touch)*. function _touch(opts, files) { opts = common.parseOptions(opts, { 'a': 'atime_only', 'c': 'no_create', 'd': 'date', 'm': 'mtime_only', 'r': 'reference', }); if (!files) { common.error('no paths given'); } if (Array.isArray(files)) { files.forEach(function(f) { touchFile(opts, f); }); } else if (typeof files === 'string') { touchFile(opts, files); } else { common.error('file arg should be a string file path or an Array of string file paths'); } } function touchFile(opts, file) { var stat = tryStatFile(file); if (stat && stat.isDirectory()) { // don't error just exit return; } // if the file doesn't already exist and the user has specified --no-create then // this script is finished if (!stat && opts.no_create) { return; } // open the file and then close it. this will create it if it doesn't exist but will // not truncate the file fs.closeSync(fs.openSync(file, 'a')); // // Set timestamps // // setup some defaults var now = new Date(); var mtime = opts.date || now; var atime = opts.date || now; // use reference file if (opts.reference) { var refStat = tryStatFile(opts.reference); if (!refStat) { common.error('failed to get attributess of ' + opts.reference); } mtime = refStat.mtime; atime = refStat.atime; } else if (opts.date) { mtime = opts.date; atime = opts.date; } if (opts.atime_only && opts.mtime_only) { // keep the new values of mtime and atime like GNU } else if (opts.atime_only) { mtime = stat.mtime; } else if (opts.mtime_only) { atime = stat.atime; } fs.utimesSync(file, atime, mtime); } module.exports = _touch; function tryStatFile(filePath) { try { return fs.statSync(filePath); } catch (e) { return null; } } var common = require('./common'); var fs = require('fs'); var path = require('path'); // XP's system default value for PATHEXT system variable, just in case it's not // set on Windows. var XP_DEFAULT_PATHEXT = '.com;.exe;.bat;.cmd;.vbs;.vbe;.js;.jse;.wsf;.wsh'; // Cross-platform method for splitting environment PATH variables function splitPath(p) { if (!p) return []; if (common.platform === 'win') return p.split(';'); else return p.split(':'); } function checkPath(path) { return fs.existsSync(path) && !fs.statSync(path).isDirectory(); } //@ //@ ### which(command) //@ //@ Examples: //@ //@ ```javascript //@ var nodeExec = which('node'); //@ ``` //@ //@ Searches for `command` in the system's PATH. On Windows, this uses the //@ `PATHEXT` variable to append the extension if it's not already executable. //@ Returns string containing the absolute path to the command. function _which(options, cmd) { if (!cmd) common.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 (common.platform === 'win') { attempt = attempt.toUpperCase(); // In case the PATHEXT variable is somehow not set (e.g. // child_process.spawn with an empty environment), use the XP default. var pathExtEnv = process.env.PATHEXT || XP_DEFAULT_PATHEXT; var pathExtArray = splitPath(pathExtEnv.toUpperCase()); var i; // If the extension is already in PATHEXT, just return that. for (i = 0; i < pathExtArray.length; i++) { var ext = pathExtArray[i]; if (attempt.slice(-ext.length) === ext && checkPath(attempt)) { where = attempt; return; } } // Cycle through the PATHEXT variable var baseAttempt = attempt; for (i = 0; i < pathExtArray.length; i++) { attempt = baseAttempt + pathExtArray[i]; if (checkPath(attempt)) { where = attempt; return; } } } else { // Assume it's Unix-like if (checkPath(attempt)) { where = attempt; return; } } }); } // Command not found anywhere? if (!checkPath(cmd) && !where) return null; where = where || path.resolve(cmd); return common.ShellString(where); } module.exports = _which;