2012-03-04 04:01:31 +09:00
//
// ShellJS
// Unix shell commands on top of Node's API
//
// Copyright (c) 2012 Artur Adib
// http://github.com/arturadib/shelljs
//
var fs = require ( 'fs' ) ,
path = require ( 'path' ) ,
util = require ( 'util' ) ,
vm = require ( 'vm' ) ,
child = require ( 'child_process' ) ,
os = require ( 'os' ) ;
// Node shims for < v0.7
fs . existsSync = fs . existsSync || path . existsSync ;
2013-03-08 01:32:16 +09:00
var config = {
silent : false ,
fatal : false
} ;
2012-03-04 04:01:31 +09:00
var state = {
error : null ,
currentCmd : 'shell.js' ,
tempDir : null
} ,
platform = os . type ( ) . match ( /^Win/ ) ? 'win' : 'unix' ;
//@
//@ All commands run synchronously, unless otherwise stated.
2013-03-08 01:32:16 +09:00
//@
2012-03-04 04:01:31 +09:00
//@
2013-03-08 01:32:16 +09:00
//@ ### cd('dir')
2012-03-04 04:01:31 +09:00
//@ Changes to directory `dir` for the duration of the script
function _cd ( options , dir ) {
if ( ! dir )
error ( 'directory not specified' ) ;
if ( ! fs . existsSync ( dir ) )
error ( 'no such file or directory: ' + dir ) ;
if ( fs . existsSync ( dir ) && ! fs . statSync ( dir ) . isDirectory ( ) )
error ( 'not a directory: ' + dir ) ;
process . chdir ( dir ) ;
2013-03-08 01:32:16 +09:00
}
2012-03-04 04:01:31 +09:00
exports . cd = wrap ( 'cd' , _cd ) ;
//@
2013-03-08 01:32:16 +09:00
//@ ### pwd()
2012-03-04 04:01:31 +09:00
//@ Returns the current directory.
function _pwd ( options ) {
var pwd = path . resolve ( process . cwd ( ) ) ;
return ShellString ( pwd ) ;
2013-03-08 01:32:16 +09:00
}
2012-03-04 04:01:31 +09:00
exports . pwd = wrap ( 'pwd' , _pwd ) ;
2012-03-15 06:50:49 +09:00
2012-03-04 04:01:31 +09:00
//@
2013-03-08 01:32:16 +09:00
//@ ### ls([options ,] path [,path ...])
//@ ### ls([options ,] path_array)
2012-03-04 04:01:31 +09:00
//@ Available options:
//@
//@ + `-R`: recursive
2013-03-08 01:32:16 +09:00
//@ + `-A`: all files (include files beginning with `.`, except for `.` and `..`)
2012-03-04 04:01:31 +09:00
//@
//@ Examples:
//@
//@ ```javascript
//@ ls('projs/*.js');
//@ ls('-R', '/users/me', '/tmp');
//@ ls('-R', ['/users/me', '/tmp']); // same as above
//@ ```
//@
2012-03-27 05:33:00 +09:00
//@ Returns array of files in the given path, or in current directory if no path provided.
2012-03-04 04:01:31 +09:00
function _ls ( options , paths ) {
options = parseOptions ( options , {
'R' : 'recursive' ,
2013-03-08 01:32:16 +09:00
'A' : 'all' ,
'a' : 'all_deprecated'
2012-03-04 04:01:31 +09:00
} ) ;
2013-03-08 01:32:16 +09:00
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
log ( 'ls: Option -a is deprecated. Use -A instead' ) ;
options . all = true ;
}
2012-03-04 04:01:31 +09:00
if ( ! paths )
paths = [ '.' ] ;
else if ( typeof paths === 'object' )
paths = paths ; // assume array
else if ( typeof paths === 'string' )
paths = [ ] . slice . call ( arguments , 1 ) ;
2012-03-27 05:33:00 +09:00
var list = [ ] ;
2012-03-04 04:01:31 +09:00
2012-03-27 05:33:00 +09:00
// 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 ) {
2012-03-04 04:01:31 +09:00
// 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 ) )
2012-03-27 05:33:00 +09:00
return false ;
2012-03-04 04:01:31 +09:00
}
2012-03-27 05:33:00 +09:00
if ( platform === 'win' )
file = file . replace ( /\\/g , '/' ) ;
list . push ( file ) ;
return true ;
2012-03-04 04:01:31 +09:00
}
paths . forEach ( function ( p ) {
if ( fs . existsSync ( p ) ) {
// Simple file?
if ( fs . statSync ( p ) . isFile ( ) ) {
2012-03-27 05:33:00 +09:00
pushFile ( p , p ) ;
2012-03-04 04:01:31 +09:00
return ; // continue
}
2013-03-08 01:32:16 +09:00
2012-03-04 04:01:31 +09:00
// Simple dir?
if ( fs . statSync ( p ) . isDirectory ( ) ) {
// Iterate over p contents
fs . readdirSync ( p ) . forEach ( function ( file ) {
2012-03-27 05:33:00 +09:00
if ( ! pushFile ( file , p ) )
return ;
// Recursive?
if ( options . recursive ) {
var oldDir = _pwd ( ) ;
_cd ( '' , p ) ;
if ( fs . statSync ( file ) . isDirectory ( ) )
2013-03-08 01:32:16 +09:00
list = list . concat ( _ls ( '-R' + ( options . all ? 'A' : '' ) , file + '/*' ) ) ;
2012-03-27 05:33:00 +09:00
_cd ( '' , oldDir ) ;
}
2012-03-04 04:01:31 +09:00
} ) ;
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
2013-03-08 01:32:16 +09:00
var regexp = basename . replace ( /(\^|\$|\(|\)|<|>|\[|\]|\{|\}|\.|\+|\?)/g , '\\$1' ) ;
2012-03-04 04:01:31 +09:00
// Translates wildcard into regex
2012-03-27 05:33:00 +09:00
regexp = '^' + regexp . replace ( /\*/g , '.*' ) + '$' ;
2012-03-04 04:01:31 +09:00
// Iterate over directory contents
fs . readdirSync ( dirname ) . forEach ( function ( file ) {
if ( file . match ( new RegExp ( regexp ) ) ) {
2012-03-27 05:33:00 +09:00
if ( ! pushFile ( path . normalize ( dirname + '/' + file ) , basename ) )
return ;
// Recursive?
if ( options . recursive ) {
var pp = dirname + '/' + file ;
if ( fs . statSync ( pp ) . isDirectory ( ) )
2013-03-08 01:32:16 +09:00
list = list . concat ( _ls ( '-R' + ( options . all ? 'A' : '' ) , pp + '/*' ) ) ;
2012-03-27 05:33:00 +09:00
} // recursive
} // if file matches
2012-03-04 04:01:31 +09:00
} ) ; // forEach
return ;
}
error ( 'no such file or directory: ' + p , true ) ;
} ) ;
2012-03-27 05:33:00 +09:00
return list ;
2013-03-08 01:32:16 +09:00
}
2012-03-04 04:01:31 +09:00
exports . ls = wrap ( 'ls' , _ls ) ;
2012-03-15 06:50:49 +09:00
//@
2013-03-08 01:32:16 +09:00
//@ ### find(path [,path ...])
//@ ### find(path_array)
2012-03-15 06:50:49 +09:00
//@ Examples:
//@
//@ ```javascript
//@ find('src', 'lib');
//@ find(['src', 'lib']); // same as above
2012-03-27 05:33:00 +09:00
//@ find('.').filter(function(file) { return file.match(/\.js$/); });
2012-03-15 06:50:49 +09:00
//@ ```
//@
2012-03-27 05:33:00 +09:00
//@ Returns array of all files (however deep) in the given paths.
2012-03-15 06:50:49 +09:00
//@
2013-03-08 01:32:16 +09:00
//@ The main difference from `ls('-R', path)` is that the resulting file names
2012-03-15 06:50:49 +09:00
//@ include the base directories, e.g. `lib/resources/file1` instead of just `file1`.
function _find ( options , paths ) {
if ( ! paths )
error ( 'no path specified' ) ;
else if ( typeof paths === 'object' )
paths = paths ; // assume array
else if ( typeof paths === 'string' )
paths = [ ] . slice . call ( arguments , 1 ) ;
2012-03-27 05:33:00 +09:00
var list = [ ] ;
function pushFile ( file ) {
if ( platform === 'win' )
file = file . replace ( /\\/g , '/' ) ;
list . push ( file ) ;
}
2012-03-15 06:50:49 +09:00
// 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
2012-03-27 05:33:00 +09:00
paths . forEach ( function ( file ) {
pushFile ( file ) ;
2012-03-15 06:50:49 +09:00
if ( fs . statSync ( file ) . isDirectory ( ) ) {
2013-03-08 01:32:16 +09:00
_ls ( '-RA' , file + '/*' ) . forEach ( function ( subfile ) {
2012-03-27 05:33:00 +09:00
pushFile ( subfile ) ;
} ) ;
2012-03-15 06:50:49 +09:00
}
} ) ;
2012-03-27 05:33:00 +09:00
return list ;
2012-03-15 06:50:49 +09:00
}
exports . find = wrap ( 'find' , _find ) ;
2012-03-04 04:01:31 +09:00
//@
2013-03-08 01:32:16 +09:00
//@ ### cp([options ,] source [,source ...], dest)
//@ ### cp([options ,] source_array, dest)
2012-03-04 04:01:31 +09:00
//@ Available options:
//@
//@ + `-f`: force
//@ + `-r, -R`: recursive
//@
//@ Examples:
//@
//@ ```javascript
//@ cp('file1', 'dir1');
//@ cp('-Rf', '/tmp/*', '/usr/local/*', '/home/tmp');
//@ cp('-Rf', ['/tmp/*', '/usr/local/*'], '/home/tmp'); // same as above
//@ ```
//@
//@ Copies files. The wildcard `*` is accepted.
function _cp ( options , sources , dest ) {
options = parseOptions ( options , {
'f' : 'force' ,
'R' : 'recursive' ,
'r' : 'recursive'
} ) ;
// Get sources, dest
if ( arguments . length < 3 ) {
error ( 'missing <source> and/or <dest>' ) ;
} else if ( arguments . length > 3 ) {
sources = [ ] . slice . call ( arguments , 1 , arguments . length - 1 ) ;
dest = arguments [ arguments . length - 1 ] ;
} else if ( typeof sources === 'string' ) {
sources = [ sources ] ;
} else if ( 'length' in sources ) {
sources = sources ; // no-op for array
} else {
error ( 'invalid arguments' ) ;
}
// Dest is not existing dir, but multiple sources given
if ( ( ! fs . existsSync ( dest ) || ! fs . statSync ( dest ) . isDirectory ( ) ) && sources . length > 1 )
error ( 'dest is not a directory (too many sources)' ) ;
// Dest is an existing file, but no -f given
if ( fs . existsSync ( dest ) && fs . statSync ( dest ) . isFile ( ) && ! options . force )
error ( 'dest file already exists: ' + dest ) ;
2013-03-08 01:32:16 +09:00
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 ] += '*' ;
} ) ;
// 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
}
}
2012-03-04 04:01:31 +09:00
sources = expand ( sources ) ;
sources . forEach ( function ( src ) {
if ( ! fs . existsSync ( src ) ) {
error ( 'no such file or directory: ' + src , true ) ;
return ; // skip file
}
// If here, src exists
if ( fs . statSync ( src ) . isDirectory ( ) ) {
if ( ! options . recursive ) {
// Non-Recursive
log ( src + ' is a directory (not copied)' ) ;
} else {
// Recursive
// 'cp /a/source dest' should create 'source' in 'dest'
2013-03-08 01:32:16 +09:00
var newDest = path . join ( dest , path . basename ( src ) ) ,
2012-03-04 04:01:31 +09:00
checkDir = fs . statSync ( src ) ;
try {
fs . mkdirSync ( newDest , checkDir . mode ) ;
} catch ( e ) {
//if the directory already exists, that's okay
if ( e . code !== 'EEXIST' ) throw e ;
}
2013-03-08 01:32:16 +09:00
2012-03-04 04:01:31 +09:00
cpdirSyncRecursive ( src , newDest , { force : options . force } ) ;
}
return ; // done with dir
}
// If here, src is a file
// When copying to '/path/dir':
// thisDest = '/path/dir/file1'
var thisDest = dest ;
if ( fs . existsSync ( dest ) && fs . statSync ( dest ) . isDirectory ( ) )
thisDest = path . normalize ( dest + '/' + path . basename ( src ) ) ;
if ( fs . existsSync ( thisDest ) && ! options . force ) {
error ( 'dest file already exists: ' + thisDest , true ) ;
return ; // skip file
}
copyFileSync ( src , thisDest ) ;
} ) ; // forEach(src)
2013-03-08 01:32:16 +09:00
}
2012-03-04 04:01:31 +09:00
exports . cp = wrap ( 'cp' , _cp ) ;
//@
2013-03-08 01:32:16 +09:00
//@ ### rm([options ,] file [, file ...])
//@ ### rm([options ,] file_array)
2012-03-04 04:01:31 +09:00
//@ 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
//@ ```
//@
2013-03-08 01:32:16 +09:00
//@ Removes files. The wildcard `*` is accepted.
2012-03-04 04:01:31 +09:00
function _rm ( options , files ) {
options = parseOptions ( options , {
'f' : 'force' ,
'r' : 'recursive' ,
'R' : 'recursive'
} ) ;
if ( ! files )
error ( 'no paths given' ) ;
if ( typeof files === 'string' )
files = [ ] . slice . call ( arguments , 1 ) ;
// if it's array leave it as it is
files = expand ( files ) ;
files . forEach ( function ( file ) {
if ( ! fs . existsSync ( file ) ) {
// Path does not exist, no force flag given
if ( ! options . force )
error ( 'no such file or directory: ' + file , true ) ;
return ; // skip file
}
// If here, path exists
// Remove simple file
if ( fs . statSync ( file ) . isFile ( ) ) {
2012-03-27 05:33:00 +09:00
// Do not check for file writing permissions
if ( options . force ) {
_unlinkSync ( file ) ;
return ;
}
2013-03-08 01:32:16 +09:00
2012-03-27 05:33:00 +09:00
if ( isWriteable ( file ) )
_unlinkSync ( file ) ;
else
error ( 'permission denied: ' + file , true ) ;
2012-03-04 04:01:31 +09:00
return ;
2012-03-27 05:33:00 +09:00
} // simple file
2012-03-04 04:01:31 +09:00
// Path is an existing directory, but no -r flag given
if ( fs . statSync ( file ) . isDirectory ( ) && ! options . recursive ) {
error ( 'path is a directory' , true ) ;
return ; // skip path
}
// Recursively remove existing directory
if ( fs . statSync ( file ) . isDirectory ( ) && options . recursive ) {
2012-03-27 05:33:00 +09:00
rmdirSyncRecursive ( file , options . force ) ;
2012-03-04 04:01:31 +09:00
}
} ) ; // forEach(file)
2013-03-08 01:32:16 +09:00
} // rm
2012-03-04 04:01:31 +09:00
exports . rm = wrap ( 'rm' , _rm ) ;
//@
2013-03-08 01:32:16 +09:00
//@ ### mv(source [, source ...], dest')
//@ ### mv(source_array, dest')
2012-03-04 04:01:31 +09:00
//@ Available options:
//@
//@ + `f`: force
//@
//@ Examples:
//@
//@ ```javascript
//@ mv('-f', 'file', 'dir/');
//@ mv('file1', 'file2', 'dir/');
//@ mv(['file1', 'file2'], 'dir/'); // same as above
//@ ```
//@
//@ Moves files. The wildcard `*` is accepted.
function _mv ( options , sources , dest ) {
options = parseOptions ( options , {
'f' : 'force'
} ) ;
// Get sources, dest
if ( arguments . length < 3 ) {
error ( 'missing <source> and/or <dest>' ) ;
} else if ( arguments . length > 3 ) {
sources = [ ] . slice . call ( arguments , 1 , arguments . length - 1 ) ;
dest = arguments [ arguments . length - 1 ] ;
} else if ( typeof sources === 'string' ) {
sources = [ sources ] ;
} else if ( 'length' in sources ) {
sources = sources ; // no-op for array
} else {
error ( 'invalid arguments' ) ;
}
sources = expand ( sources ) ;
// Dest is not existing dir, but multiple sources given
if ( ( ! fs . existsSync ( dest ) || ! fs . statSync ( dest ) . isDirectory ( ) ) && sources . length > 1 )
error ( 'dest is not a directory (too many sources)' ) ;
// Dest is an existing file, but no -f given
if ( fs . existsSync ( dest ) && fs . statSync ( dest ) . isFile ( ) && ! options . force )
error ( 'dest file already exists: ' + dest ) ;
sources . forEach ( function ( src ) {
if ( ! fs . existsSync ( src ) ) {
error ( 'no such file or directory: ' + src , true ) ;
return ; // skip file
}
// If here, src exists
// When copying to '/path/dir':
// thisDest = '/path/dir/file1'
var thisDest = dest ;
if ( fs . existsSync ( dest ) && fs . statSync ( dest ) . isDirectory ( ) )
thisDest = path . normalize ( dest + '/' + path . basename ( src ) ) ;
if ( fs . existsSync ( thisDest ) && ! options . force ) {
error ( 'dest file already exists: ' + thisDest , true ) ;
return ; // skip file
}
if ( path . resolve ( src ) === path . dirname ( path . resolve ( thisDest ) ) ) {
error ( 'cannot move to self: ' + src , true ) ;
return ; // skip file
}
fs . renameSync ( src , thisDest ) ;
} ) ; // forEach(src)
2013-03-08 01:32:16 +09:00
} // mv
2012-03-04 04:01:31 +09:00
exports . mv = wrap ( 'mv' , _mv ) ;
//@
2013-03-08 01:32:16 +09:00
//@ ### mkdir([options ,] dir [, dir ...])
//@ ### mkdir([options ,] dir_array)
2012-03-04 04:01:31 +09:00
//@ Available options:
//@
//@ + `p`: full path (will create intermediate dirs if necessary)
//@
//@ Examples:
//@
//@ ```javascript
//@ mkdir('-p', '/tmp/a/b/c/d', '/tmp/e/f/g');
//@ mkdir('-p', ['/tmp/a/b/c/d', '/tmp/e/f/g']); // same as above
//@ ```
//@
//@ Creates directories.
function _mkdir ( options , dirs ) {
options = parseOptions ( options , {
'p' : 'fullpath'
} ) ;
if ( ! dirs )
error ( 'no paths given' ) ;
if ( typeof dirs === 'string' )
dirs = [ ] . slice . call ( arguments , 1 ) ;
// if it's array leave it as it is
dirs . forEach ( function ( dir ) {
if ( fs . existsSync ( dir ) ) {
if ( ! options . fullpath )
error ( 'path already exists: ' + dir , true ) ;
return ; // skip dir
}
// Base dir does not exist, and no -p option given
var baseDir = path . dirname ( dir ) ;
if ( ! fs . existsSync ( baseDir ) && ! options . fullpath ) {
error ( 'no such file or directory: ' + baseDir , true ) ;
return ; // skip dir
}
if ( options . fullpath )
mkdirSyncRecursive ( dir ) ;
else
2013-03-08 01:32:16 +09:00
fs . mkdirSync ( dir , parseInt ( '0777' , 8 ) ) ;
2012-03-04 04:01:31 +09:00
} ) ;
2013-03-08 01:32:16 +09:00
} // mkdir
2012-03-04 04:01:31 +09:00
exports . mkdir = wrap ( 'mkdir' , _mkdir ) ;
2012-03-15 06:50:49 +09:00
//@
2013-03-08 01:32:16 +09:00
//@ ### test(expression)
2012-03-15 06:50:49 +09:00
//@ Available expression primaries:
//@
2013-03-08 01:32:16 +09:00
//@ + `'-b', 'path'`: true if path is a block device
//@ + `'-c', 'path'`: true if path is a character device
2012-03-15 06:50:49 +09:00
//@ + `'-d', 'path'`: true if path is a directory
2013-03-08 01:32:16 +09:00
//@ + `'-e', 'path'`: true if path exists
2012-03-15 06:50:49 +09:00
//@ + `'-f', 'path'`: true if path is a regular file
2013-03-08 01:32:16 +09:00
//@ + `'-L', 'path'`: true if path is a symboilc link
//@ + `'-p', 'path'`: true if path is a pipe (FIFO)
//@ + `'-S', 'path'`: true if path is a socket
2012-03-15 06:50:49 +09:00
//@
//@ 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 )
error ( 'no path given' ) ;
// hack - only works with unary primaries
options = parseOptions ( options , {
2013-03-08 01:32:16 +09:00
'b' : 'block' ,
'c' : 'character' ,
2012-03-15 06:50:49 +09:00
'd' : 'directory' ,
2013-03-08 01:32:16 +09:00
'e' : 'exists' ,
'f' : 'file' ,
'L' : 'link' ,
'p' : 'pipe' ,
'S' : 'socket'
2012-03-15 06:50:49 +09:00
} ) ;
2013-03-08 01:32:16 +09:00
var canInterpret = false ;
for ( var key in options )
if ( options [ key ] === true ) {
canInterpret = true ;
break ;
}
if ( ! canInterpret )
2012-03-15 06:50:49 +09:00
error ( 'could not interpret expression' ) ;
2013-03-08 01:32:16 +09:00
if ( ! fs . existsSync ( path ) )
return false ;
if ( options . exists )
return true ;
if ( options . link )
return fs . lstatSync ( path ) . isSymbolicLink ( ) ;
var stats = fs . statSync ( path ) ;
if ( options . block )
return stats . isBlockDevice ( ) ;
if ( options . character )
return stats . isCharacterDevice ( ) ;
2012-03-15 06:50:49 +09:00
if ( options . directory )
2013-03-08 01:32:16 +09:00
return stats . isDirectory ( ) ;
2012-03-15 06:50:49 +09:00
if ( options . file )
2013-03-08 01:32:16 +09:00
return stats . isFile ( ) ;
if ( options . pipe )
return stats . isFIFO ( ) ;
if ( options . socket )
return stats . isSocket ( ) ;
} // test
2012-03-15 06:50:49 +09:00
exports . test = wrap ( 'test' , _test ) ;
2012-03-04 04:01:31 +09:00
//@
2013-03-08 01:32:16 +09:00
//@ ### cat(file [, file ...])
//@ ### cat(file_array)
2012-03-04 04:01:31 +09:00
//@
//@ Examples:
//@
//@ ```javascript
//@ var str = cat('file*.txt');
//@ var str = cat('file1', 'file2');
//@ var str = cat(['file1', 'file2']); // same as above
//@ ```
//@
//@ Returns a string containing the given file, or a concatenated string
//@ containing the files if more than one file is given (a new line character is
//@ introduced between each file). Wildcard `*` accepted.
function _cat ( options , files ) {
var cat = '' ;
if ( ! files )
error ( 'no paths given' ) ;
if ( typeof files === 'string' )
files = [ ] . slice . call ( arguments , 1 ) ;
// if it's array leave it as it is
files = expand ( files ) ;
files . forEach ( function ( file ) {
if ( ! fs . existsSync ( file ) )
error ( 'no such file or directory: ' + file ) ;
cat += fs . readFileSync ( file , 'utf8' ) + '\n' ;
} ) ;
if ( cat [ cat . length - 1 ] === '\n' )
cat = cat . substring ( 0 , cat . length - 1 ) ;
return ShellString ( cat ) ;
2013-03-08 01:32:16 +09:00
}
2012-03-04 04:01:31 +09:00
exports . cat = wrap ( 'cat' , _cat ) ;
//@
2013-03-08 01:32:16 +09:00
//@ ### 'string'.to(file)
2012-03-04 04:01:31 +09:00
//@
//@ Examples:
//@
//@ ```javascript
//@ cat('input.txt').to('output.txt');
//@ ```
//@
//@ Analogous to the redirection operator `>` in Unix, but works with JavaScript strings (such as
//@ those returned by `cat`, `grep`, etc). _Like Unix redirections, `to()` will overwrite any existing file!_
function _to ( options , file ) {
if ( ! file )
error ( 'wrong arguments' ) ;
if ( ! fs . existsSync ( path . dirname ( file ) ) )
error ( 'no such file or directory: ' + path . dirname ( file ) ) ;
2012-03-27 05:33:00 +09:00
try {
fs . writeFileSync ( file , this . toString ( ) , 'utf8' ) ;
} catch ( e ) {
error ( 'could not write to file (code ' + e . code + '): ' + file , true ) ;
}
2013-03-08 01:32:16 +09:00
}
// In the future, when Proxies are default, we can add methods like `.to()` to primitive strings.
2012-03-04 04:01:31 +09:00
// For now, this is a dummy function to bookmark places we need such strings
function ShellString ( str ) {
return str ;
}
String . prototype . to = wrap ( 'to' , _to ) ;
//@
2013-03-08 01:32:16 +09:00
//@ ### sed([options ,] search_regex, replace_str, file)
2012-03-04 04:01:31 +09:00
//@ Available options:
//@
//@ + `-i`: Replace contents of 'file' in-place. _Note that no backups will be created!_
//@
//@ Examples:
//@
//@ ```javascript
//@ sed('-i', 'PROGRAM_VERSION', 'v0.1.3', 'source.js');
//@ sed(/.*DELETE_THIS_LINE.*\n/, '', 'source.js');
//@ ```
//@
//@ Reads an input string from `file` and performs a JavaScript `replace()` on the input
//@ using the given search regex and replacement string. Returns the new string after replacement.
function _sed ( options , regex , replacement , file ) {
options = parseOptions ( options , {
'i' : 'inplace'
} ) ;
if ( typeof replacement === 'string' )
replacement = replacement ; // no-op
else if ( typeof replacement === 'number' )
replacement = replacement . toString ( ) ; // fallback
else
error ( 'invalid replacement string' ) ;
if ( ! file )
error ( 'no file given' ) ;
if ( ! fs . existsSync ( file ) )
error ( 'no such file or directory: ' + file ) ;
var result = fs . readFileSync ( file , 'utf8' ) . replace ( regex , replacement ) ;
if ( options . inplace )
fs . writeFileSync ( file , result , 'utf8' ) ;
return ShellString ( result ) ;
2013-03-08 01:32:16 +09:00
}
2012-03-04 04:01:31 +09:00
exports . sed = wrap ( 'sed' , _sed ) ;
//@
2013-03-08 01:32:16 +09:00
//@ ### 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.
2012-03-04 04:01:31 +09:00
//@
//@ Examples:
//@
//@ ```javascript
2013-03-08 01:32:16 +09:00
//@ grep('-v', 'GLOBAL_VARIABLE', '*.js');
2012-03-04 04:01:31 +09:00
//@ grep('GLOBAL_VARIABLE', '*.js');
//@ ```
//@
2013-03-08 01:32:16 +09:00
//@ Reads input string from given files and returns a string containing all lines of the
2012-03-04 04:01:31 +09:00
//@ file that match the given `regex_filter`. Wildcard `*` accepted.
function _grep ( options , regex , files ) {
2013-03-08 01:32:16 +09:00
options = parseOptions ( options , {
'v' : 'inverse'
} ) ;
2012-03-04 04:01:31 +09:00
if ( ! files )
error ( 'no paths given' ) ;
if ( typeof files === 'string' )
files = [ ] . slice . call ( arguments , 2 ) ;
// if it's array leave it as it is
files = expand ( files ) ;
var grep = '' ;
files . forEach ( function ( file ) {
if ( ! fs . existsSync ( file ) ) {
error ( 'no such file or directory: ' + file , true ) ;
return ;
}
var contents = fs . readFileSync ( file , 'utf8' ) ,
lines = contents . split ( /\r*\n/ ) ;
lines . forEach ( function ( line ) {
2013-03-08 01:32:16 +09:00
var matched = line . match ( regex ) ;
if ( ( options . inverse && ! matched ) || ( ! options . inverse && matched ) )
2012-03-04 04:01:31 +09:00
grep += line + '\n' ;
} ) ;
} ) ;
return ShellString ( grep ) ;
2013-03-08 01:32:16 +09:00
}
2012-03-04 04:01:31 +09:00
exports . grep = wrap ( 'grep' , _grep ) ;
//@
2013-03-08 01:32:16 +09:00
//@ ### which(command)
2012-03-04 04:01:31 +09:00
//@
//@ Examples:
//@
//@ ```javascript
//@ var nodeExec = which('node');
//@ ```
//@
//@ Searches for `command` in the system's PATH. On Windows looks for `.exe`, `.cmd`, and `.bat` extensions.
//@ Returns string containing the absolute path to the command.
function _which ( options , cmd ) {
if ( ! cmd )
error ( 'must specify command' ) ;
var pathEnv = process . env . path || process . env . Path || process . env . PATH ,
pathArray = splitPath ( pathEnv ) ,
where = null ;
// No relative/absolute paths provided?
if ( cmd . search ( /\// ) === - 1 ) {
// Search for command in PATH
pathArray . forEach ( function ( dir ) {
if ( where )
return ; // already found it
var attempt = path . resolve ( dir + '/' + cmd ) ;
if ( fs . existsSync ( attempt ) ) {
where = attempt ;
return ;
}
if ( platform === 'win' ) {
var baseAttempt = attempt ;
attempt = baseAttempt + '.exe' ;
if ( fs . existsSync ( attempt ) ) {
where = attempt ;
return ;
}
attempt = baseAttempt + '.cmd' ;
if ( fs . existsSync ( attempt ) ) {
where = attempt ;
return ;
}
attempt = baseAttempt + '.bat' ;
if ( fs . existsSync ( attempt ) ) {
where = attempt ;
return ;
}
} // if 'win'
} ) ;
}
2013-03-08 01:32:16 +09:00
2012-03-04 04:01:31 +09:00
// Command not found anywhere?
if ( ! fs . existsSync ( cmd ) && ! where )
return null ;
where = where || path . resolve ( cmd ) ;
return ShellString ( where ) ;
2013-03-08 01:32:16 +09:00
}
2012-03-04 04:01:31 +09:00
exports . which = wrap ( 'which' , _which ) ;
//@
2013-03-08 01:32:16 +09:00
//@ ### echo(string [,string ...])
2012-03-04 04:01:31 +09:00
//@
//@ Examples:
//@
//@ ```javascript
//@ echo('hello world');
//@ var str = echo('hello world');
//@ ```
//@
//@ Prints string to stdout, and returns string with additional utility methods
//@ like `.to()`.
2013-03-08 01:32:16 +09:00
function _echo ( ) {
var messages = [ ] . slice . call ( arguments , 0 ) ;
2012-03-29 07:03:31 +09:00
console . log . apply ( this , messages ) ;
2012-03-04 04:01:31 +09:00
return ShellString ( messages . join ( ' ' ) ) ;
2013-03-08 01:32:16 +09:00
}
exports . echo = _echo ; // don't wrap() as it could parse '-options'
2012-03-04 04:01:31 +09:00
2013-03-08 01:32:16 +09:00
// 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 {
error ( index + ': directory stack index out of range' ) ;
}
} else {
error ( index + ': invalid number' ) ;
}
}
function _actualDirStack ( ) {
return [ process . cwd ( ) ] . concat ( _dirStack ) ;
}
//@
//@ ### 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.
2012-03-04 04:01:31 +09:00
//@
2013-03-08 01:32:16 +09:00
//@ 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 = parseOptions ( options , {
'c' : 'clear'
} ) ;
if ( options [ 'clear' ] ) {
return ( _dirStack = [ ] ) ;
}
var stack = _actualDirStack ( ) ;
if ( index ) {
index = _parseStackIndex ( index ) ;
if ( index < 0 ) {
index = stack . length + index ;
}
log ( stack [ index ] ) ;
return stack [ index ] ;
}
log ( stack . join ( ' ' ) ) ;
return stack ;
}
exports . dirs = wrap ( "dirs" , _dirs ) ;
//@
//@ ### 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 = 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 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 = wrap ( '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 = parseOptions ( options , {
'n' : 'no-cd'
} ) ;
if ( ! _dirStack . length ) {
return 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 = wrap ( "popd" , _popd ) ;
//@
//@ ### exit(code)
2012-03-04 04:01:31 +09:00
//@ Exits the current process with the given exit code.
exports . exit = process . exit ;
//@
2013-03-08 01:32:16 +09:00
//@ ### env['VAR_NAME']
2012-03-04 04:01:31 +09:00
//@ Object containing environment variables (both getter and setter). Shortcut to process.env.
exports . env = process . env ;
//@
2013-03-08 01:32:16 +09:00
//@ ### exec(command [, options] [, callback])
2012-03-04 04:01:31 +09:00
//@ Available options (all `false` by default):
//@
2013-03-08 01:32:16 +09:00
//@ + `async`: Asynchronous execution. Defaults to true if a callback is provided.
2012-03-04 04:01:31 +09:00
//@ + `silent`: Do not echo program output to console.
//@
//@ Examples:
//@
//@ ```javascript
//@ var version = exec('node --version', {silent:true}).output;
2013-03-08 01:32:16 +09:00
//@
//@ 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, output) {
//@ console.log('Exit code:', code);
//@ console.log('Program output:', output);
//@ });
2012-03-04 04:01:31 +09:00
//@ ```
//@
2013-03-08 01:32:16 +09:00
//@ Executes the given `command` _synchronously_, unless otherwise specified.
//@ When in synchronous mode returns the object `{ code:..., output:... }`, containing the program's
//@ `output` (stdout + stderr) and its exit `code`. Otherwise returns the child process object, and
//@ the `callback` gets the arguments `(code, output)`.
2012-03-29 07:03:31 +09:00
//@
//@ **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.
2012-03-04 04:01:31 +09:00
function _exec ( command , options , callback ) {
if ( ! command )
error ( 'must specify command' ) ;
if ( typeof options === 'function' ) {
callback = options ;
2013-03-08 01:32:16 +09:00
options = { async : true } ;
2012-03-04 04:01:31 +09:00
}
options = extend ( {
2013-03-08 01:32:16 +09:00
silent : config . silent ,
2012-03-04 04:01:31 +09:00
async : false
} , options ) ;
if ( options . async )
2013-03-08 01:32:16 +09:00
return execAsync ( command , options , callback ) ;
2012-03-04 04:01:31 +09:00
else
return execSync ( command , options ) ;
2013-03-08 01:32:16 +09:00
}
2012-03-04 04:01:31 +09:00
exports . exec = wrap ( 'exec' , _exec , { notUnix : true } ) ;
2013-03-08 01:32:16 +09:00
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 ,
2012-03-04 04:01:31 +09:00
2013-03-08 01:32:16 +09:00
// 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
} ) ;
2012-03-04 04:01:31 +09:00
//@
2013-03-08 01:32:16 +09:00
//@ ### chmod(octal_mode || octal_string, file)
//@ ### chmod(symbolic_mode, file)
//@
//@ Available options:
2012-03-04 04:01:31 +09:00
//@
2013-03-08 01:32:16 +09:00
//@ + `-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 {
error ( 'You must specify a file.' ) ;
}
}
2012-03-04 04:01:31 +09:00
2013-03-08 01:32:16 +09:00
options = parseOptions ( options , {
'R' : 'recursive' ,
'c' : 'changes' ,
'v' : 'verbose'
} ) ;
2012-03-04 04:01:31 +09:00
2013-03-08 01:32:16 +09:00
if ( typeof filePattern === 'string' ) {
filePattern = [ filePattern ] ;
}
2012-03-04 04:01:31 +09:00
2013-03-08 01:32:16 +09:00
var files ;
2012-03-04 04:01:31 +09:00
2013-03-08 01:32:16 +09:00
if ( options . recursive ) {
files = [ ] ;
expand ( filePattern ) . forEach ( function addFile ( expandedFile ) {
var stat = fs . lstatSync ( expandedFile ) ;
2012-03-04 04:01:31 +09:00
2013-03-08 01:32:16 +09:00
if ( ! stat . isSymbolicLink ( ) ) {
files . push ( expandedFile ) ;
2012-03-04 04:01:31 +09:00
2013-03-08 01:32:16 +09:00
if ( stat . isDirectory ( ) ) { // intentionally does not follow symlinks.
fs . readdirSync ( expandedFile ) . forEach ( function ( child ) {
addFile ( expandedFile + '/' + child ) ;
} ) ;
}
}
} ) ;
}
else {
files = expand ( filePattern ) ;
}
2012-03-04 04:01:31 +09:00
2013-03-08 01:32:16 +09:00
files . forEach ( function innerChmod ( file ) {
file = path . resolve ( file ) ;
if ( ! fs . existsSync ( file ) ) {
error ( 'File not found: ' + file ) ;
}
2012-03-27 05:33:00 +09:00
2013-03-08 01:32:16 +09:00
// When recursing, don't follow symlinks.
if ( options . recursive && fs . lstatSync ( file ) . isSymbolicLink ( ) ) {
return ;
}
var perms = fs . statSync ( file ) . 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 changeSticky = change . indexOf ( 't' ) != - 1 ;
var changeSetuid = change . indexOf ( 's' ) != - 1 ;
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 ) {
log ( file + ' -> ' + newPerms . toString ( 8 ) ) ;
}
if ( perms != newPerms ) {
if ( ! options . verbose && options . changes ) {
log ( file + ' -> ' + newPerms . toString ( 8 ) ) ;
}
fs . chmodSync ( file , newPerms ) ;
}
}
else {
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 ) ;
}
} ) ;
2012-03-27 05:33:00 +09:00
}
2013-03-08 01:32:16 +09:00
exports . chmod = wrap ( 'chmod' , _chmod ) ;
2012-03-27 05:33:00 +09:00
//@
2013-03-08 01:32:16 +09:00
//@ ## Configuration
//@
exports . config = config ;
//@
//@ ### config.silent
2012-03-27 05:33:00 +09:00
//@ Example:
//@
//@ ```javascript
2013-03-08 01:32:16 +09:00
//@ var silentState = config.silent; // save old silent state
//@ config.silent = true;
2012-03-27 05:33:00 +09:00
//@ /* ... */
2013-03-08 01:32:16 +09:00
//@ config.silent = silentState; // restore old silent state
2012-03-27 05:33:00 +09:00
//@ ```
//@
2013-03-08 01:32:16 +09:00
//@ Suppresses all command output if `true`, except for `echo()` calls.
//@ Default is `false`.
2012-03-27 05:33:00 +09:00
//@
2013-03-08 01:32:16 +09:00
//@ ### config.fatal
//@ Example:
2012-03-27 05:33:00 +09:00
//@
2013-03-08 01:32:16 +09:00
//@ ```javascript
//@ config.fatal = true;
//@ cp('this_file_does_not_exist', '/dev/null'); // dies here
//@ /* more commands... */
//@ ```
//@
//@ If `true` the script will die on errors. Default is `false`.
2012-03-27 05:33:00 +09:00
2012-03-04 04:01:31 +09:00
//@
2013-03-08 01:32:16 +09:00
//@ ## Non-Unix commands
2012-03-27 05:33:00 +09:00
//@
2012-03-04 04:01:31 +09:00
//@
2013-03-08 01:32:16 +09:00
//@ ### tempdir()
//@ Searches and returns string containing a writeable, platform-dependent temporary directory.
//@ Follows Python's [tempfile algorithm](http://docs.python.org/library/tempfile.html#tempfile.tempdir).
exports . tempdir = wrap ( 'tempdir' , tempDir ) ;
2012-03-04 04:01:31 +09:00
2013-03-08 01:32:16 +09:00
//@
//@ ### 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 ;
} ;
2012-03-04 04:01:31 +09:00
////////////////////////////////////////////////////////////////////////////////////////////////
//
// Auxiliary functions (internal use only)
//
function log ( ) {
2013-03-08 01:32:16 +09:00
if ( ! config . silent )
2012-03-04 04:01:31 +09:00
console . log . apply ( this , arguments ) ;
}
2012-03-27 05:33:00 +09:00
function deprecate ( what , msg ) {
console . log ( '*** ShellJS.' + what + ': This function is deprecated.' , msg ) ;
}
2012-03-04 04:01:31 +09:00
function write ( msg ) {
2013-03-08 01:32:16 +09:00
if ( ! config . silent )
2012-03-04 04:01:31 +09:00
process . stdout . write ( msg ) ;
}
2013-03-08 01:32:16 +09:00
// Shows error message. Throws unless _continue or config.fatal are true
2012-03-04 04:01:31 +09:00
function error ( msg , _continue ) {
if ( state . error === null )
state . error = '' ;
state . error += state . currentCmd + ': ' + msg + '\n' ;
2013-03-08 01:32:16 +09:00
2012-03-04 04:01:31 +09:00
log ( state . error ) ;
2013-03-08 01:32:16 +09:00
if ( config . fatal )
process . exit ( 1 ) ;
2012-03-04 04:01:31 +09:00
if ( ! _continue )
throw '' ;
}
// Returns {'alice': true, 'bob': false} when passed:
// parseOptions('-a', {'a':'alice', 'b':'bob'});
function parseOptions ( str , map ) {
if ( ! map )
error ( 'parseOptions() internal error: no map given' ) ;
// All options are false by default
var options = { } ;
2013-03-08 01:32:16 +09:00
for ( var letter in map )
2012-03-04 04:01:31 +09:00
options [ map [ letter ] ] = false ;
if ( ! str )
return options ; // defaults
if ( typeof str !== 'string' )
error ( 'parseOptions() internal error: wrong str' ) ;
// e.g. match[1] = 'Rf' for str = '-Rf'
var match = str . match ( /^\-(.+)/ ) ;
if ( ! match )
return options ;
// e.g. chars = ['R', 'f']
var chars = match [ 1 ] . split ( '' ) ;
chars . forEach ( function ( char ) {
if ( char in map )
options [ map [ char ] ] = true ;
else
error ( 'option not recognized: ' + char ) ;
} ) ;
return options ;
}
// Common wrapper for all Unix-like commands
function wrap ( cmd , fn , options ) {
return function ( ) {
var retValue = null ;
state . currentCmd = cmd ;
state . error = null ;
try {
var args = [ ] . slice . call ( arguments , 0 ) ;
if ( options && options . notUnix ) {
retValue = fn . apply ( this , args ) ;
} else {
if ( args . length === 0 || typeof args [ 0 ] !== 'string' || args [ 0 ] [ 0 ] !== '-' )
args . unshift ( '' ) ; // only add dummy option if '-option' not already present
retValue = fn . apply ( this , args ) ;
}
} catch ( e ) {
if ( ! state . error ) {
// If state.error hasn't been set it's an error thrown by Node, not us - probably a bug...
2012-03-29 07:03:31 +09:00
console . log ( 'shell.js: internal error' ) ;
2012-03-04 04:01:31 +09:00
console . log ( e . stack || e ) ;
process . exit ( 1 ) ;
}
2013-03-08 01:32:16 +09:00
if ( config . fatal )
2012-03-04 04:01:31 +09:00
throw e ;
}
2012-03-29 07:03:31 +09:00
state . currentCmd = 'shell.js' ;
2012-03-04 04:01:31 +09:00
return retValue ;
2013-03-08 01:32:16 +09:00
} ;
2012-03-04 04:01:31 +09:00
} // wrap
// Buffered file copy, synchronous
// (Using readFileSync() + writeFileSync() could easily cause a memory overflow
// with large files)
function copyFileSync ( srcFile , destFile ) {
if ( ! fs . existsSync ( srcFile ) )
error ( 'copyFileSync: no such file or directory: ' + srcFile ) ;
var BUF _LENGTH = 64 * 1024 ,
buf = new Buffer ( BUF _LENGTH ) ,
bytesRead = BUF _LENGTH ,
2012-03-27 05:33:00 +09:00
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 ) ;
}
2012-03-04 04:01:31 +09:00
while ( bytesRead === BUF _LENGTH ) {
bytesRead = fs . readSync ( fdr , buf , 0 , BUF _LENGTH , pos ) ;
fs . writeSync ( fdw , buf , 0 , bytesRead ) ;
pos += bytesRead ;
}
fs . closeSync ( fdr ) ;
fs . closeSync ( fdw ) ;
}
// Recursively copies 'sourceDir' into 'destDir'
// Adapted from https://github.com/ryanmcgrath/wrench-js
//
// Copyright (c) 2010 Ryan McGrath
// Copyright (c) 2012 Artur Adib
//
// Licensed under the MIT License
// http://www.opensource.org/licenses/mit-license.php
function cpdirSyncRecursive ( sourceDir , destDir , opts ) {
if ( ! opts ) opts = { } ;
/* Create the directory where all our junk is moving to; read the mode of the source directory and mirror it */
var checkDir = fs . statSync ( sourceDir ) ;
try {
fs . mkdirSync ( destDir , checkDir . mode ) ;
} catch ( e ) {
//if the directory already exists, that's okay
if ( e . code !== 'EEXIST' ) throw e ;
}
var files = fs . readdirSync ( sourceDir ) ;
for ( var i = 0 ; i < files . length ; i ++ ) {
var currFile = fs . lstatSync ( sourceDir + "/" + files [ i ] ) ;
if ( currFile . isDirectory ( ) ) {
/* recursion this thing right on back. */
cpdirSyncRecursive ( sourceDir + "/" + files [ i ] , destDir + "/" + files [ i ] , opts ) ;
} else if ( currFile . isSymbolicLink ( ) ) {
var symlinkFull = fs . readlinkSync ( sourceDir + "/" + files [ i ] ) ;
fs . symlinkSync ( symlinkFull , destDir + "/" + files [ i ] ) ;
} else {
/* At this point, we've hit a file actually worth copying... so copy it on over. */
if ( fs . existsSync ( destDir + "/" + files [ i ] ) && ! opts . force ) {
log ( 'skipping existing file: ' + files [ i ] ) ;
} else {
copyFileSync ( sourceDir + "/" + files [ i ] , destDir + "/" + files [ i ] ) ;
}
}
} // for files
2013-03-08 01:32:16 +09:00
} // cpdirSyncRecursive
2012-03-04 04:01:31 +09:00
// 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
2012-03-27 05:33:00 +09:00
function rmdirSyncRecursive ( dir , force ) {
2012-03-04 04:01:31 +09:00
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 ++ ) {
2012-03-27 05:33:00 +09:00
var file = dir + "/" + files [ i ] ,
currFile = fs . lstatSync ( file ) ;
2012-03-04 04:01:31 +09:00
2012-03-27 05:33:00 +09:00
if ( currFile . isDirectory ( ) ) { // Recursive function back to the beginning
rmdirSyncRecursive ( file , force ) ;
}
2012-03-04 04:01:31 +09:00
2012-03-27 05:33:00 +09:00
else if ( currFile . isSymbolicLink ( ) ) { // Unlink symlinks
2013-03-08 01:32:16 +09:00
if ( force || isWriteable ( file ) ) {
try {
_unlinkSync ( file ) ;
} catch ( e ) {
error ( 'could not remove file (code ' + e . code + '): ' + file , true ) ;
}
}
2012-03-27 05:33:00 +09:00
}
2012-03-04 04:01:31 +09:00
else // Assume it's a file - perhaps a try/catch belongs here?
2013-03-08 01:32:16 +09:00
if ( force || isWriteable ( file ) ) {
try {
_unlinkSync ( file ) ;
} catch ( e ) {
error ( 'could not remove file (code ' + e . code + '): ' + file , true ) ;
}
}
2012-03-04 04:01:31 +09:00
}
2013-03-08 01:32:16 +09:00
// Now that we know everything in the sub-tree has been deleted, we can delete the main directory.
2012-03-04 04:01:31 +09:00
// Huzzah for the shopkeep.
2012-03-27 05:33:00 +09:00
var result ;
try {
result = fs . rmdirSync ( dir ) ;
} catch ( e ) {
2012-03-29 07:03:31 +09:00
error ( 'could not remove directory (code ' + e . code + '): ' + dir , true ) ;
2012-03-27 05:33:00 +09:00
}
return result ;
2013-03-08 01:32:16 +09:00
} // rmdirSyncRecursive
2012-03-04 04:01:31 +09:00
// Recursively creates 'dir'
function mkdirSyncRecursive ( dir ) {
var baseDir = path . dirname ( dir ) ;
// Base dir exists, no recursion necessary
if ( fs . existsSync ( baseDir ) ) {
2013-03-08 01:32:16 +09:00
fs . mkdirSync ( dir , parseInt ( '0777' , 8 ) ) ;
2012-03-04 04:01:31 +09:00
return ;
}
// Base dir does not exist, go recursive
mkdirSyncRecursive ( baseDir ) ;
// Base dir created, can create dir
2013-03-08 01:32:16 +09:00
fs . mkdirSync ( dir , parseInt ( '0777' , 8 ) ) ;
}
2012-03-04 04:01:31 +09:00
// e.g. 'makerjs_a5f185d0443ca...'
function randomFileName ( ) {
function randomHash ( count ) {
if ( count === 1 )
2013-03-08 01:32:16 +09:00
return parseInt ( 16 * Math . random ( ) , 10 ) . toString ( 16 ) ;
2012-03-04 04:01:31 +09:00
else {
var hash = '' ;
for ( var i = 0 ; i < count ; i ++ )
hash += randomHash ( 1 ) ;
return hash ;
}
}
return 'makerjs_' + randomHash ( 20 ) ;
}
// Returns false if 'dir' is not a writeable directory, 'dir' otherwise
function writeableDir ( dir ) {
if ( ! dir || ! fs . existsSync ( dir ) )
return false ;
if ( ! fs . statSync ( dir ) . isDirectory ( ) )
return false ;
var testFile = dir + '/' + randomFileName ( ) ;
try {
fs . writeFileSync ( testFile , ' ' ) ;
2012-03-27 05:33:00 +09:00
_unlinkSync ( testFile ) ;
2012-03-04 04:01:31 +09:00
return dir ;
} catch ( e ) {
return false ;
}
}
// Cross-platform method for getting an available temporary directory.
// Follows the algorithm of Python's tempfile.tempdir
// http://docs.python.org/library/tempfile.html#tempfile.tempdir
function tempDir ( ) {
if ( state . tempDir )
return state . tempDir ; // from cache
state . tempDir = writeableDir ( process . env [ 'TMPDIR' ] ) ||
writeableDir ( process . env [ 'TEMP' ] ) ||
writeableDir ( process . env [ 'TMP' ] ) ||
writeableDir ( process . env [ 'Wimp$ScrapDir' ] ) || // RiscOS
writeableDir ( 'C:\\TEMP' ) || // Windows
writeableDir ( 'C:\\TMP' ) || // Windows
writeableDir ( '\\TEMP' ) || // Windows
writeableDir ( '\\TMP' ) || // Windows
writeableDir ( '/tmp' ) ||
writeableDir ( '/var/tmp' ) ||
writeableDir ( '/usr/tmp' ) ||
writeableDir ( '.' ) ; // last resort
2013-03-08 01:32:16 +09:00
2012-03-04 04:01:31 +09:00
return state . tempDir ;
}
// Wrapper around exec() to enable echoing output to console in real time
function execAsync ( cmd , opts , callback ) {
2012-03-29 07:03:31 +09:00
var output = '' ;
var options = extend ( {
2013-03-08 01:32:16 +09:00
silent : config . silent
2012-03-29 07:03:31 +09:00
} , opts ) ;
2013-03-08 01:32:16 +09:00
2012-03-04 04:01:31 +09:00
var c = child . exec ( cmd , { env : process . env } , function ( err ) {
2013-03-08 01:32:16 +09:00
if ( callback )
2012-03-04 04:01:31 +09:00
callback ( err ? err . code : 0 , output ) ;
} ) ;
c . stdout . on ( 'data' , function ( data ) {
output += data ;
2012-03-29 07:03:31 +09:00
if ( ! options . silent )
process . stdout . write ( data ) ;
2012-03-04 04:01:31 +09:00
} ) ;
c . stderr . on ( 'data' , function ( data ) {
output += data ;
2012-03-29 07:03:31 +09:00
if ( ! options . silent )
process . stdout . write ( data ) ;
2012-03-04 04:01:31 +09:00
} ) ;
2013-03-08 01:32:16 +09:00
return c ;
2012-03-04 04:01:31 +09:00
}
// 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
2013-03-08 01:32:16 +09:00
// Node is single-threaded; callbacks and other internal state changes are done in the
2012-03-04 04:01:31 +09:00
// event loop).
function execSync ( cmd , opts ) {
var stdoutFile = path . resolve ( tempDir ( ) + '/' + randomFileName ( ) ) ,
codeFile = path . resolve ( tempDir ( ) + '/' + randomFileName ( ) ) ,
2012-03-27 05:33:00 +09:00
scriptFile = path . resolve ( tempDir ( ) + '/' + randomFileName ( ) ) ,
sleepFile = path . resolve ( tempDir ( ) + '/' + randomFileName ( ) ) ;
2012-03-04 04:01:31 +09:00
var options = extend ( {
2013-03-08 01:32:16 +09:00
silent : config . silent
2012-03-04 04:01:31 +09:00
} , opts ) ;
var previousStdoutContent = '' ;
// Echoes stdout changes from running process, if not silent
function updateStdout ( ) {
2012-03-27 05:33:00 +09:00
if ( options . silent || ! fs . existsSync ( stdoutFile ) )
2012-03-04 04:01:31 +09:00
return ;
var stdoutContent = fs . readFileSync ( stdoutFile , 'utf8' ) ;
// No changes since last time?
if ( stdoutContent . length <= previousStdoutContent . length )
return ;
process . stdout . write ( stdoutContent . substr ( previousStdoutContent . length ) ) ;
previousStdoutContent = stdoutContent ;
}
function escape ( str ) {
2013-03-08 01:32:16 +09:00
return ( str + '' ) . replace ( /([\\"'])/g , "\\$1" ) . replace ( /\0/g , "\\0" ) ;
2012-03-04 04:01:31 +09:00
}
2013-03-08 01:32:16 +09:00
2012-03-04 04:01:31 +09:00
cmd += ' > ' + stdoutFile + ' 2>&1' ; // works on both win/unix
2013-03-08 01:32:16 +09:00
var script =
"var child = require('child_process')," +
" fs = require('fs');" +
"child.exec('" + escape ( cmd ) + "', {env: process.env}, function(err) {" +
" fs.writeFileSync('" + escape ( codeFile ) + "', err ? err.code.toString() : '0');" +
"});" ;
2012-03-04 04:01:31 +09:00
2012-03-27 05:33:00 +09:00
if ( fs . existsSync ( scriptFile ) ) _unlinkSync ( scriptFile ) ;
if ( fs . existsSync ( stdoutFile ) ) _unlinkSync ( stdoutFile ) ;
if ( fs . existsSync ( codeFile ) ) _unlinkSync ( codeFile ) ;
2012-03-04 04:01:31 +09:00
fs . writeFileSync ( scriptFile , script ) ;
2013-03-08 01:32:16 +09:00
child . exec ( '"' + process . execPath + '" ' + scriptFile , {
2012-03-04 04:01:31 +09:00
env : process . env ,
cwd : exports . pwd ( )
} ) ;
// The wait loop
2012-03-27 05:33:00 +09:00
// 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)
2013-03-08 01:32:16 +09:00
while ( ! fs . existsSync ( codeFile ) ) { updateStdout ( ) ; fs . writeFileSync ( sleepFile , 'a' ) ; }
while ( ! fs . existsSync ( stdoutFile ) ) { updateStdout ( ) ; fs . writeFileSync ( sleepFile , 'a' ) ; }
2012-03-04 04:01:31 +09:00
// At this point codeFile exists, but it's not necessarily flushed yet.
// Keep reading it until it is.
2013-03-08 01:32:16 +09:00
var code = parseInt ( '' , 10 ) ;
while ( isNaN ( code ) ) {
code = parseInt ( fs . readFileSync ( codeFile , 'utf8' ) , 10 ) ;
}
2012-03-04 04:01:31 +09:00
var stdout = fs . readFileSync ( stdoutFile , 'utf8' ) ;
2012-03-29 07:03:31 +09:00
// No biggie if we can't erase the files now -- they're in a temp dir anyway
2013-03-08 01:32:16 +09:00
try { _unlinkSync ( scriptFile ) ; } catch ( e ) { }
try { _unlinkSync ( stdoutFile ) ; } catch ( e ) { }
try { _unlinkSync ( codeFile ) ; } catch ( e ) { }
try { _unlinkSync ( sleepFile ) ; } catch ( e ) { }
2012-03-04 04:01:31 +09:00
// True if successful, false if not
var obj = {
code : code ,
output : stdout
} ;
return obj ;
} // execSync()
2013-03-08 01:32:16 +09:00
// Expands wildcards with matching file names. For a given array of file names 'list', returns
// another array containing all file names as per ls(list[i]).
// For example:
// expand(['file*.js']) = ['file1.js', 'file2.js', ...]
// (if the files 'file1.js', 'file2.js', etc, exist in the current dir)
2012-03-04 04:01:31 +09:00
function expand ( list ) {
var expanded = [ ] ;
list . forEach ( function ( listEl ) {
2013-03-08 01:32:16 +09:00
// Wildcard present?
2012-03-04 04:01:31 +09:00
if ( listEl . search ( /\*/ ) > - 1 ) {
2012-03-27 05:33:00 +09:00
_ls ( '' , listEl ) . forEach ( function ( file ) {
2012-03-04 04:01:31 +09:00
expanded . push ( file ) ;
2012-03-27 05:33:00 +09:00
} ) ;
2012-03-04 04:01:31 +09:00
} else {
expanded . push ( listEl ) ;
}
2013-03-08 01:32:16 +09:00
} ) ;
2012-03-04 04:01:31 +09:00
return expanded ;
}
// Cross-platform method for splitting environment PATH variables
function splitPath ( p ) {
if ( ! p )
return [ ] ;
if ( platform === 'win' )
return p . split ( ';' ) ;
else
return p . split ( ':' ) ;
}
// extend(target_obj, source_obj1 [, source_obj2 ...])
// Shallow extend, e.g.:
2013-03-08 01:32:16 +09:00
// extend({A:1}, {b:2}, {c:3}) returns {A:1, b:2, c:3}
2012-03-04 04:01:31 +09:00
function extend ( target ) {
var sources = [ ] . slice . call ( arguments , 1 ) ;
sources . forEach ( function ( source ) {
2013-03-08 01:32:16 +09:00
for ( var key in source )
2012-03-04 04:01:31 +09:00
target [ key ] = source [ key ] ;
} ) ;
2013-03-08 01:32:16 +09:00
2012-03-04 04:01:31 +09:00
return target ;
}
2012-03-27 05:33:00 +09:00
// 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 ;
}