@ -74,9 +74,7 @@ exports.pwd = wrap('pwd', _pwd);
@@ -74,9 +74,7 @@ exports.pwd = wrap('pwd', _pwd);
//@ ls('-R', ['/users/me', '/tmp']); // same as above
//@ ```
//@
//@ Returns list of files in the given path, or in current directory if no path provided.
//@ For convenient iteration via `for (file in ls())`, the format returned is a hash object:
//@ `{ 'file1':null, 'dir1/file2':null, ...}`.
//@ Returns array of files in the given path, or in current directory if no path provided.
function _ls ( options , paths ) {
options = parseOptions ( options , {
'R' : 'recursive' ,
@ -90,24 +88,30 @@ function _ls(options, paths) {
@@ -90,24 +88,30 @@ function _ls(options, paths) {
else if ( typeof paths === 'string' )
paths = [ ] . slice . call ( arguments , 1 ) ;
var hash = { } ;
var list = [ ] ;
function pushHash ( file , query ) {
// 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 ) {
// hidden file?
if ( path . basename ( file ) [ 0 ] === '.' ) {
// not explicitly asking for hidden files?
if ( ! options . all && ! ( path . basename ( query ) [ 0 ] === '.' && path . basename ( query ) . length > 1 ) )
return ;
return false ;
}
hash [ file ] = null ;
if ( platform === 'win' )
file = file . replace ( /\\/g , '/' ) ;
list . push ( file ) ;
return true ;
}
paths . forEach ( function ( p ) {
if ( fs . existsSync ( p ) ) {
// Simple file?
if ( fs . statSync ( p ) . isFile ( ) ) {
pushHash ( p , p ) ;
pushFile ( p , p ) ;
return ; // continue
}
@ -115,14 +119,17 @@ function _ls(options, paths) {
@@ -115,14 +119,17 @@ function _ls(options, paths) {
if ( fs . statSync ( p ) . isDirectory ( ) ) {
// Iterate over p contents
fs . readdirSync ( p ) . forEach ( function ( file ) {
pushHash ( file , p ) ;
// Recursive
var oldDir = _pwd ( ) ;
_cd ( '' , p ) ;
if ( fs . statSync ( file ) . isDirectory ( ) && options . recursive )
hash = extend ( hash , _ls ( '-R' , file + '/*' ) ) ;
_cd ( '' , oldDir ) ;
if ( ! pushFile ( file , p ) )
return ;
// Recursive?
if ( options . recursive ) {
var oldDir = _pwd ( ) ;
_cd ( '' , p ) ;
if ( fs . statSync ( file ) . isDirectory ( ) )
list = list . concat ( _ls ( '-R' + ( options . all ? 'a' : '' ) , file + '/*' ) ) ;
_cd ( '' , oldDir ) ;
}
} ) ;
return ; // continue
}
@ -137,17 +144,20 @@ function _ls(options, paths) {
@@ -137,17 +144,20 @@ function _ls(options, paths) {
// Escape special regular expression chars
var regexp = basename . replace ( /(\^|\$|\(|\)|\<|\>|\[|\]|\{|\}|\.|\+|\?)/g , '\\$1' ) ;
// Translates wildcard into regex
regexp = '^' + regexp . replace ( /\*/g , '.*' ) ;
regexp = '^' + regexp . replace ( /\*/g , '.*' ) + '$' ;
// Iterate over directory contents
fs . readdirSync ( dirname ) . forEach ( function ( file ) {
if ( file . match ( new RegExp ( regexp ) ) ) {
pushHash ( path . normalize ( dirname + '/' + file ) , basename ) ;
// Recursive
var pp = dirname + '/' + file ;
if ( fs . statSync ( pp ) . isDirectory ( ) && options . recursive )
hash = extend ( hash , _ls ( '-R' , pp + '/*' ) ) ;
}
if ( ! pushFile ( path . normalize ( dirname + '/' + file ) , basename ) )
return ;
// Recursive?
if ( options . recursive ) {
var pp = dirname + '/' + file ;
if ( fs . statSync ( pp ) . isDirectory ( ) )
list = list . concat ( _ls ( '-R' + ( options . all ? 'a' : '' ) , pp + '/*' ) ) ;
} // recursive
} // if file matches
} ) ; // forEach
return ;
}
@ -155,7 +165,7 @@ function _ls(options, paths) {
@@ -155,7 +165,7 @@ function _ls(options, paths) {
error ( 'no such file or directory: ' + p , true ) ;
} ) ;
return hash ;
return list ;
} ;
exports . ls = wrap ( 'ls' , _ls ) ;
@ -168,16 +178,10 @@ exports.ls = wrap('ls', _ls);
@@ -168,16 +178,10 @@ exports.ls = wrap('ls', _ls);
//@ ```javascript
//@ find('src', 'lib');
//@ find(['src', 'lib']); // same as above
//@ for (file in find('.')) {
//@ if (!file.match(/\.js$/))
//@ continue;
//@ // all files at this point end in '.js'
//@ }
//@ find('.').filter(function(file) { return file.match(/\.js$/); });
//@ ```
//@
//@ Returns list of all files (however deep) in the given paths. For convenient iteration
//@ via `for (file in find(...))`, the format returned is a hash object:
//@ `{ 'file1':null, 'dir1/file2':null, ...}`.
//@ 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`.
@ -189,21 +193,28 @@ function _find(options, paths) {
@@ -189,21 +193,28 @@ function _find(options, paths) {
else if ( typeof paths === 'string' )
paths = [ ] . slice . call ( arguments , 1 ) ;
var hash = { } ;
var list = [ ] ;
function pushFile ( file ) {
if ( 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 ) {
hash [ file ] = null ;
paths . forEach ( function ( file ) {
pushFile ( file ) ;
if ( fs . statSync ( file ) . isDirectory ( ) ) {
for ( subfile in _ls ( '-Ra' , file + '/*' ) )
hash [ subfile ] = null ;
_ls ( '-Ra' , file + '/*' ) . forEach ( function ( subfile ) {
pushFile ( subfile ) ;
} ) ;
}
} ) ;
return hash ;
return list ;
}
exports . find = wrap ( 'find' , _find ) ;
@ -347,9 +358,20 @@ function _rm(options, files) {
@@ -347,9 +358,20 @@ function _rm(options, files) {
// Remove simple file
if ( fs . statSync ( file ) . isFile ( ) ) {
fs . unlinkSync ( file ) ;
// Do not check for file writing permissions
if ( options . force ) {
_unlinkSync ( file ) ;
return ;
}
if ( isWriteable ( file ) )
_unlinkSync ( file ) ;
else
error ( 'permission denied: ' + file , true ) ;
return ;
}
} // simple file
// Path is an existing directory, but no -r flag given
if ( fs . statSync ( file ) . isDirectory ( ) && ! options . recursive ) {
@ -359,7 +381,7 @@ function _rm(options, files) {
@@ -359,7 +381,7 @@ function _rm(options, files) {
// Recursively remove existing directory
if ( fs . statSync ( file ) . isDirectory ( ) && options . recursive ) {
rmdirSyncRecursive ( file ) ;
rmdirSyncRecursive ( file , options . force ) ;
}
} ) ; // forEach(file)
} ; // rm
@ -582,7 +604,11 @@ function _to(options, file) {
@@ -582,7 +604,11 @@ function _to(options, file) {
if ( ! fs . existsSync ( path . dirname ( file ) ) )
error ( 'no such file or directory: ' + path . dirname ( file ) ) ;
fs . writeFileSync ( file , this . toString ( ) , 'utf8' ) ;
try {
fs . writeFileSync ( file , this . toString ( ) , 'utf8' ) ;
} catch ( e ) {
error ( 'could not write to file (code ' + e . code + '): ' + file , true ) ;
}
} ;
// 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
@ -751,7 +777,7 @@ exports.which = wrap('which', _which);
@@ -751,7 +777,7 @@ exports.which = wrap('which', _which);
//@ like `.to()`.
function _echo ( options ) {
var messages = [ ] . slice . call ( arguments , 1 ) ;
log . apply ( this , messages ) ;
console . log . apply ( this , messages ) ;
return ShellString ( messages . join ( ' ' ) ) ;
} ;
exports . echo = wrap ( 'echo' , _echo ) ;
@ -783,6 +809,10 @@ exports.env = process.env;
@@ -783,6 +809,10 @@ exports.env = process.env;
//@ When in synchronous mode returns the object `{ code:..., output:... }`, containing the program's
//@ `output` (stdout + stderr) and its exit `code`. Otherwise the `callback` gets the
//@ arguments `(code, output)`.
//@
//@ **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 )
error ( 'must specify command' ) ;
@ -793,7 +823,7 @@ function _exec(command, options, callback) {
@@ -793,7 +823,7 @@ function _exec(command, options, callback) {
}
options = extend ( {
silent : false ,
silent : state . silent ,
async : false
} , options ) ;
@ -822,11 +852,53 @@ exports.exec = wrap('exec', _exec, {notUnix:true});
@@ -822,11 +852,53 @@ exports.exec = wrap('exec', _exec, {notUnix:true});
//@ Follows Python's [tempfile algorithm](http://docs.python.org/library/tempfile.html#tempfile.tempdir).
exports . tempdir = wrap ( 'tempdir' , tempDir ) ;
//@
//@ #### error()
//@ Tests if error occurred in the last command. Returns `null` if no error occurred,
//@ otherwise returns string explaining the error
exports . error = function ( ) {
return state . error ;
}
//@
//@ #### silent([state])
//@ Example:
//@
//@ ```javascript
//@ var silentState = silent();
//@ silent(true);
//@ /* ... */
//@ silent(silentState); // restore old silent state
//@ ```
//@
//@ Suppresses all command output if `state = true`, except for `echo()` calls.
//@ Returns state if no arguments given.
exports . silent = function ( _state ) {
if ( typeof _state !== 'boolean' )
return state . silent ;
state . silent = _state ;
}
//@
//@ ## Deprecated
//@
//@
//@ #### exists(path [, path ...])
//@ #### exists(path_array)
//@
//@ _This function is being deprecated. Use `test()` instead._
//@
//@ Returns true if all the given paths exist.
function _exists ( options , paths ) {
deprecate ( 'exists' , 'Use test() instead.' ) ;
if ( ! paths )
error ( 'no paths given' ) ;
@ -844,26 +916,17 @@ function _exists(options, paths) {
@@ -844,26 +916,17 @@ function _exists(options, paths) {
} ;
exports . exists = wrap ( 'exists' , _exists ) ;
//@
//@ #### error()
//@ Tests if error occurred in the last command. Returns `null` if no error occurred,
//@ otherwise returns string explaining the error
exports . error = function ( ) {
return state . error ;
}
//@
//@ #### verbose()
//@
//@ _This function is being deprecated. Use `silent(false) instead.`_
//@
//@ Enables all output (default)
exports . verbose = function ( ) {
state . silent = false ;
}
deprecate ( 'verbose' , 'Use silent(false) instead.' ) ;
//@
//@ #### silent()
//@ Suppresses all output, except for explict `echo()` calls
exports . silent = function ( ) {
state . silent = true ;
state . silent = false ;
}
@ -875,10 +938,6 @@ exports.silent = function() {
@@ -875,10 +938,6 @@ exports.silent = function() {
////////////////////////////////////////////////////////////////////////////////////////////////
//
// Auxiliary functions (internal use only)
@ -889,6 +948,10 @@ function log() {
@@ -889,6 +948,10 @@ function log() {
console . log . apply ( this , arguments ) ;
}
function deprecate ( what , msg ) {
console . log ( '*** ShellJS.' + what + ': This function is deprecated.' , msg ) ;
}
function write ( msg ) {
if ( ! state . silent )
process . stdout . write ( msg ) ;
@ -962,7 +1025,7 @@ function wrap(cmd, fn, options) {
@@ -962,7 +1025,7 @@ function wrap(cmd, fn, options) {
} catch ( e ) {
if ( ! state . error ) {
// If state.error hasn't been set it's an error thrown by Node, not us - probably a bug...
console . log ( 'maker .js: internal error' ) ;
console . log ( 'shell .js: internal error' ) ;
console . log ( e . stack || e ) ;
process . exit ( 1 ) ;
}
@ -970,7 +1033,7 @@ function wrap(cmd, fn, options) {
@@ -970,7 +1033,7 @@ function wrap(cmd, fn, options) {
throw e ;
}
state . currentCmd = 'maker .js' ;
state . currentCmd = 'shell .js' ;
return retValue ;
}
} // wrap
@ -984,10 +1047,22 @@ function copyFileSync(srcFile, destFile) {
@@ -984,10 +1047,22 @@ function copyFileSync(srcFile, destFile) {
var BUF _LENGTH = 64 * 1024 ,
buf = new Buffer ( BUF _LENGTH ) ,
fdr = fs . openSync ( srcFile , 'r' ) ,
fdw = fs . openSync ( destFile , 'w' ) ,
bytesRead = BUF _LENGTH ,
pos = 0 ;
pos = 0 ,
fdr = null ,
fdw = null ;
try {
fdr = fs . openSync ( srcFile , 'r' ) ;
} catch ( e ) {
error ( 'copyFileSync: could not read src file (' + srcFile + ')' ) ;
}
try {
fdw = fs . openSync ( destFile , 'w' ) ;
} catch ( e ) {
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 ) ;
@ -1050,28 +1125,41 @@ function cpdirSyncRecursive(sourceDir, destDir, opts) {
@@ -1050,28 +1125,41 @@ function cpdirSyncRecursive(sourceDir, destDir, opts) {
//
// Licensed under the MIT License
// http://www.opensource.org/licenses/mit-license.php
function rmdirSyncRecursive ( dir ) {
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 currFile = fs . lstatSync ( dir + "/" + files [ i ] ) ;
var file = dir + "/" + files [ i ] ,
currFile = fs . lstatSync ( file ) ;
if ( currFile . isDirectory ( ) ) // Recursive function back to the beginning
rmdirSyncRecursive ( dir + "/" + files [ i ] ) ;
if ( currFile . isDirectory ( ) ) { // Recursive function back to the beginning
rmdirSyncRecursive ( file , force ) ;
}
else if ( currFile . isSymbolicLink ( ) ) // Unlink symlinks
fs . unlinkSync ( dir + "/" + files [ i ] ) ;
else if ( currFile . isSymbolicLink ( ) ) { // Unlink symlinks
if ( force || isWriteable ( file ) )
_unlinkSync ( file ) ;
}
else // Assume it's a file - perhaps a try/catch belongs here?
fs . unlinkSync ( dir + "/" + files [ i ] ) ;
if ( force || isWriteable ( file ) )
_unlinkSync ( file ) ;
}
// Now that we know everything in the sub-tree has been deleted, we can delete the main directory.
// Huzzah for the shopkeep.
return fs . rmdirSync ( dir ) ;
var result ;
try {
result = fs . rmdirSync ( dir ) ;
} catch ( e ) {
error ( 'could not remove directory (code ' + e . code + '): ' + dir , true ) ;
}
return result ;
} ; // rmdirSyncRecursive
// Recursively creates 'dir'
@ -1118,7 +1206,7 @@ function writeableDir(dir) {
@@ -1118,7 +1206,7 @@ function writeableDir(dir) {
var testFile = dir + '/' + randomFileName ( ) ;
try {
fs . writeFileSync ( testFile , ' ' ) ;
fs . unlinkSync ( testFile ) ;
_ unlinkSync( testFile ) ;
return dir ;
} catch ( e ) {
return false ;
@ -1151,6 +1239,10 @@ function tempDir() {
@@ -1151,6 +1239,10 @@ function tempDir() {
// Wrapper around exec() to enable echoing output to console in real time
function execAsync ( cmd , opts , callback ) {
var output = '' ;
var options = extend ( {
silent : state . silent
} , opts ) ;
var c = child . exec ( cmd , { env : process . env } , function ( err ) {
if ( callback )
@ -1159,14 +1251,14 @@ function execAsync(cmd, opts, callback) {
@@ -1159,14 +1251,14 @@ function execAsync(cmd, opts, callback) {
c . stdout . on ( 'data' , function ( data ) {
output += data ;
if ( ! opts . silent )
write ( data ) ;
if ( ! option s . silent )
process . stdout . write ( data ) ;
} ) ;
c . stderr . on ( 'data' , function ( data ) {
output += data ;
if ( ! opts . silent )
write ( data ) ;
if ( ! option s . silent )
process . stdout . write ( data ) ;
} ) ;
}
@ -1178,16 +1270,17 @@ function execAsync(cmd, opts, callback) {
@@ -1178,16 +1270,17 @@ function execAsync(cmd, opts, callback) {
function execSync ( cmd , opts ) {
var stdoutFile = path . resolve ( tempDir ( ) + '/' + randomFileName ( ) ) ,
codeFile = path . resolve ( tempDir ( ) + '/' + randomFileName ( ) ) ,
scriptFile = path . resolve ( tempDir ( ) + '/' + randomFileName ( ) ) ;
scriptFile = path . resolve ( tempDir ( ) + '/' + randomFileName ( ) ) ,
sleepFile = path . resolve ( tempDir ( ) + '/' + randomFileName ( ) ) ;
var options = extend ( {
silent : false
silent : state . silent
} , opts ) ;
var previousStdoutContent = '' ;
// Echoes stdout changes from running process, if not silent
function updateStdout ( ) {
if ( state . silent || options . silent || ! fs . existsSync ( stdoutFile ) )
if ( options . silent || ! fs . existsSync ( stdoutFile ) )
return ;
var stdoutContent = fs . readFileSync ( stdoutFile , 'utf8' ) ;
@ -1214,9 +1307,9 @@ function execSync(cmd, opts) {
@@ -1214,9 +1307,9 @@ function execSync(cmd, opts) {
fs . writeFileSync ( '"+escape(codeFile)+"' , err ? err . code . toString ( ) : '0' ) ; \
} ) ; " ;
if ( fs . existsSync ( scriptFile ) ) fs . unlinkSync ( scriptFile ) ;
if ( fs . existsSync ( stdoutFile ) ) fs . unlinkSync ( stdoutFile ) ;
if ( fs . existsSync ( codeFile ) ) fs . unlinkSync ( codeFile ) ;
if ( fs . existsSync ( scriptFile ) ) _ unlinkSync( scriptFile ) ;
if ( fs . existsSync ( stdoutFile ) ) _ unlinkSync( stdoutFile ) ;
if ( fs . existsSync ( codeFile ) ) _ unlinkSync( codeFile ) ;
fs . writeFileSync ( scriptFile , script ) ;
child . exec ( 'node ' + scriptFile , {
@ -1225,8 +1318,11 @@ function execSync(cmd, opts) {
@@ -1225,8 +1318,11 @@ function execSync(cmd, opts) {
} ) ;
// The wait loop
while ( ! fs . existsSync ( codeFile ) ) { updateStdout ( ) ; } ;
while ( ! fs . existsSync ( stdoutFile ) ) { updateStdout ( ) ; } ;
// 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 ) ) { updateStdout ( ) ; fs . writeFileSync ( sleepFile , 'a' ) ; } ;
while ( ! fs . existsSync ( stdoutFile ) ) { updateStdout ( ) ; fs . writeFileSync ( sleepFile , 'a' ) ; } ;
// At this point codeFile exists, but it's not necessarily flushed yet.
// Keep reading it until it is.
@ -1236,10 +1332,12 @@ function execSync(cmd, opts) {
@@ -1236,10 +1332,12 @@ function execSync(cmd, opts) {
var stdout = fs . readFileSync ( stdoutFile , 'utf8' ) ;
fs . unlinkSync ( scriptFile ) ;
fs . unlinkSync ( stdoutFile ) ;
fs . unlinkSync ( codeFile ) ;
// No biggie if we can't erase the files now -- they're in a temp dir anyway
try { _unlinkSync ( scriptFile ) ; } catch ( e ) { } ;
try { _unlinkSync ( stdoutFile ) ; } catch ( e ) { } ;
try { _unlinkSync ( codeFile ) ; } catch ( e ) { } ;
try { _unlinkSync ( sleepFile ) ; } catch ( e ) { } ;
// True if successful, false if not
var obj = {
code : code ,
@ -1257,8 +1355,9 @@ function expand(list) {
@@ -1257,8 +1355,9 @@ function expand(list) {
list . forEach ( function ( listEl ) {
// Wildcard present?
if ( listEl . search ( /\*/ ) > - 1 ) {
for ( file in _ls ( '' , listEl ) )
_ls ( '' , listEl ) . forEach ( function ( file ) {
expanded . push ( file ) ;
} ) ;
} else {
expanded . push ( listEl ) ;
}
@ -1290,3 +1389,33 @@ function extend(target) {
@@ -1290,3 +1389,33 @@ function extend(target) {
return target ;
}
// Normalizes _unlinkSync() across platforms to match Unix behavior, i.e.
// file can be unlinked even if it's read-only, see joyent/node#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 ;
}
}
}
// 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 ;
}