blob: a4bd2e7e96b88ab0ad675b437fcbbfa4aab9eb06 [file] [log] [blame]
/*
* Utilities: A classic collection of JavaScript utilities
* Copyright 2112 Matthew Eernisse (mde@fleegix.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
var fs = require('fs')
, path = require('path')
, JS_PAT = /\.(js|coffee)$/
, logger;
var logger = new (function () {
var out;
try {
out = require('./logger');
}
catch (e) {
out = console;
}
this.log = function (o) {
out.log(o);
};
})();
/**
@name file
@namespace file
*/
var fileUtils = new (function () {
var _copyFile
, _copyDir
, _readDir
, _rmDir
, _watch;
// Recursively copy files and directories
_copyFile = function (fromPath, toPath, opts) {
var from = path.normalize(fromPath)
, to = path.normalize(toPath)
, options = opts || {}
, fromStat
, toStat
, destExists
, destDoesNotExistErr
, content
, filename
, dirContents
, targetDir;
fromStat = fs.statSync(from);
try {
//console.dir(to + ' destExists');
toStat = fs.statSync(to);
destExists = true;
}
catch(e) {
//console.dir(to + ' does not exist');
destDoesNotExistErr = e;
destExists = false;
}
// Destination dir or file exists, copy into (directory)
// or overwrite (file)
if (destExists) {
// If there's a rename-via-copy file/dir name passed, use it.
// Otherwise use the actual file/dir name
filename = options.rename || path.basename(from);
// Copying a directory
if (fromStat.isDirectory()) {
dirContents = fs.readdirSync(from);
targetDir = path.join(to, filename);
// We don't care if the target dir already exists
try {
fs.mkdirSync(targetDir, options.mode || 0755);
}
catch(e) {
if (e.code != 'EEXIST') {
throw e;
}
}
for (var i = 0, ii = dirContents.length; i < ii; i++) {
//console.log(dirContents[i]);
_copyFile(path.join(from, dirContents[i]), targetDir);
}
}
// Copying a file
else {
content = fs.readFileSync(from);
// Copy into dir
if (toStat.isDirectory()) {
//console.log('copy into dir ' + to);
fs.writeFileSync(path.join(to, filename), content);
}
// Overwrite file
else {
//console.log('overwriting ' + to);
fs.writeFileSync(to, content);
}
}
}
// Dest doesn't exist, can't create it
else {
throw destDoesNotExistErr;
}
};
_copyDir = function (from, to, opts) {
var createDir = opts.createDir;
};
// Return the contents of a given directory
_readDir = function (dirPath) {
var dir = path.normalize(dirPath)
, paths = []
, ret = [dir]
, msg;
try {
paths = fs.readdirSync(dir);
}
catch (e) {
msg = 'Could not read path ' + dir + '\n';
if (e.stack) {
msg += e.stack;
}
throw new Error(msg);
}
paths.forEach(function (p) {
var curr = path.join(dir, p);
var stat = fs.statSync(curr);
if (stat.isDirectory()) {
ret = ret.concat(_readDir(curr));
}
else {
ret.push(curr);
}
});
return ret;
};
// Remove the given directory
_rmDir = function (dirPath) {
var dir = path.normalize(dirPath)
, paths = [];
paths = fs.readdirSync(dir);
paths.forEach(function (p) {
var curr = path.join(dir, p);
var stat = fs.statSync(curr);
if (stat.isDirectory()) {
_rmDir(curr);
}
else {
fs.unlinkSync(curr);
}
});
fs.rmdirSync(dir);
};
// Recursively watch files with a callback
_watch = function (path, callback) {
fs.stat(path, function (err, stats) {
if (err) {
return false;
}
if (stats.isFile() && JS_PAT.test(path)) {
fs.watchFile(path, callback);
}
else if (stats.isDirectory()) {
fs.readdir(path, function (err, files) {
if (err) {
return log.fatal(err);
}
for (var f in files) {
_watch(path + '/' + files[f], callback);
}
});
}
});
};
/**
@name file#cpR
@public
@function
@description Copies a directory/file to a destination
@param {String} fromPath The source path to copy from
@param {String} toPath The destination path to copy to
@param {Object} opts Options to use
@param {Boolean} [opts.silent] If false then will log the command
*/
this.cpR = function (fromPath, toPath, options) {
var from = path.normalize(fromPath)
, to = path.normalize(toPath)
, toStat
, doesNotExistErr
, paths
, filename
, opts = options || {};
if (!opts.silent) {
logger.log('cp -r ' + fromPath + ' ' + toPath);
}
opts = {}; // Reset
if (from == to) {
throw new Error('Cannot copy ' + from + ' to itself.');
}
// Handle rename-via-copy
try {
toStat = fs.statSync(to);
}
catch(e) {
doesNotExistErr = e;
// Get abs path so it's possible to check parent dir
if (!this.isAbsolute(to)) {
to = path.join(process.cwd() , to);
}
// Save the file/dir name
filename = path.basename(to);
// See if a parent dir exists, so there's a place to put the
/// renamed file/dir (resets the destination for the copy)
to = path.dirname(to);
try {
toStat = fs.statSync(to);
}
catch(e) {}
if (toStat && toStat.isDirectory()) {
// Set the rename opt to pass to the copy func, will be used
// as the new file/dir name
opts.rename = filename;
//console.log('filename ' + filename);
}
else {
throw doesNotExistErr;
}
}
_copyFile(from, to, opts);
};
/**
@name file#mkdirP
@public
@function
@description Create the given directory(ies) using the given mode permissions
@param {String} dir The directory to create
@param {Number} mode The mode to give the created directory(ies)(Default: 0755)
*/
this.mkdirP = function (dir, mode) {
var dirPath = path.normalize(dir)
, paths = dirPath.split(/\/|\\/)
, currPath = ''
, next;
if (paths[0] == '' || /^[A-Za-z]+:/.test(paths[0])) {
currPath = paths.shift() || '/';
currPath = path.join(currPath, paths.shift());
//console.log('basedir');
}
while ((next = paths.shift())) {
if (next == '..') {
currPath = path.join(currPath, next);
continue;
}
currPath = path.join(currPath, next);
try {
//console.log('making ' + currPath);
fs.mkdirSync(currPath, mode || 0755);
}
catch(e) {
if (e.code != 'EEXIST') {
throw e;
}
}
}
};
/**
@name file#readdirR
@public
@function
@return {Array} Returns the contents as an Array, can be configured via opts.format
@description Reads the given directory returning it's contents
@param {String} dir The directory to read
@param {Object} opts Options to use
@param {String} [opts.format] Set the format to return(Default: Array)
*/
this.readdirR = function (dir, opts) {
var options = opts || {}
, format = options.format || 'array'
, ret;
ret = _readDir(dir);
return format == 'string' ? ret.join('\n') : ret;
};
/**
@name file#rmRf
@public
@function
@description Deletes the given directory/file
@param {String} p The path to delete, can be a directory or file
@param {Object} opts Options to use
@param {String} [opts.silent] If false then logs the command
*/
this.rmRf = function (p, options) {
var stat
, opts = options || {};
if (!opts.silent) {
logger.log('rm -rf ' + p);
}
try {
stat = fs.statSync(p);
if (stat.isDirectory()) {
_rmDir(p);
}
else {
fs.unlinkSync(p);
}
}
catch (e) {}
};
/**
@name file#isAbsolute
@public
@function
@return {Boolean/String} If it's absolute the first char is returned otherwise false
@description Checks if a given path is absolute or relative
@param {String} p Path to check
*/
this.isAbsolute = function (p) {
var match = /^[A-Za-z]+:\\|^\//.exec(p);
if (match && match.length) {
return match[0];
}
return false;
};
/**
@name file#absolutize
@public
@function
@return {String} Returns the absolute path for the given path
@description Returns the absolute path for the given path
@param {String} p The path to get the absolute path for
*/
this.absolutize = function (p) {
if (this.isAbsolute(p)) {
return p;
}
else {
return path.join(process.cwd(), p);
}
};
/**
Given a patern, return the base directory of it (ie. the folder
that will contain all the files matching the path).
eg. file.basedir('/test/**') => '/test/'
Path ending by '/' are considerd as folder while other are considerd
as files, eg.:
file.basedir('/test/a/') => '/test/a'
file.basedir('/test/a') => '/test'
The returned path always end with a '/' so we have:
file.basedir(file.basedir(x)) == file.basedir(x)
*/
this.basedir = function (pathParam) {
var basedir = ''
, parts
, part
, pos = 0
, p = pathParam || '';
// If the path has a leading asterisk, basedir is the current dir
if (p.indexOf('*') == 0 || p.indexOf('**') == 0) {
return '.';
}
// always consider .. at the end as a folder and not a filename
if (/(?:^|\/|\\)\.\.$/.test(p.slice(-3))) {
p += '/';
}
parts = p.split(/\\|\//);
for (var i = 0, l = parts.length - 1; i < l; i++) {
part = parts[i];
if (part.indexOf('*') > -1 || part.indexOf('**') > -1) {
break;
}
pos += part.length + 1;
basedir += part + p[pos - 1];
}
if (!basedir) {
basedir = '.';
}
// Strip trailing slashes
if (!(basedir == '\\' || basedir == '/')) {
basedir = basedir.replace(/\\$|\/$/, '');
}
return basedir;
};
/**
@name file#searchParentPath
@public
@function
@description Search for a directory/file in the current directory and parent directories
@param {String} p The path to search for
@param {Function} callback The function to call once the path is found
*/
this.searchParentPath = function (location, beginPath, callback) {
if (typeof beginPath === 'function' && !callback) {
callback = beginPath;
beginPath = process.cwd();
}
var cwd = beginPath || process.cwd();
if (!location) {
// Return if no path is given
return;
}
var relPath = ''
, i = 5 // Only search up to 5 directories
, pathLoc
, pathExists;
while (--i >= 0) {
pathLoc = path.join(cwd, relPath, location);
pathExists = this.existsSync(pathLoc);
if (pathExists) {
callback && callback(undefined, pathLoc);
break;
} else {
// Dir could not be found
if (i === 0) {
callback && callback(new Error("Path \"" + pathLoc + "\" not found"), undefined);
break;
}
// Add a relative parent directory
relPath += '../';
// Switch to relative parent directory
process.chdir(path.join(cwd, relPath));
}
}
};
/**
@name file#watch
@public
@function
@description Watch a given path then calls the callback once a change occurs
@param {String} path The path to watch
@param {Function} callback The function to call when a change occurs
*/
this.watch = function () {
_watch.apply(this, arguments);
};
// Compatibility for fs.exists(0.8) and path.exists(0.6)
this.exists = (typeof fs.exists === 'function') ? fs.exists : path.exists;
// Compatibility for fs.existsSync(0.8) and path.existsSync(0.6)
this.existsSync = (typeof fs.existsSync === 'function') ? fs.existsSync : path.existsSync;
/**
@name file#requireLocal
@public
@function
@return {Object} The given module is returned
@description Require a local module from the node_modules in the current directory
@param {String} module The module to require
@param {String} message An option message to throw if the module doesn't exist
*/
this.requireLocal = function (module, message) {
// Try to require in the application directory
try {
dep = require(path.join(process.cwd(), 'node_modules', module));
}
catch(err) {
if (message) {
throw new Error(message);
}
throw new Error('Module "' + module + '" could not be found as a ' +
'local module.\n Please make sure there is a node_modules directory in the ' +
'current directory,\n and install it by doing "npm install ' +
module + '"');
}
return dep;
};
})();
module.exports = fileUtils;