fs-utils
Version:
fs extras and utilities to extend the node.js file system module. Used in Assemble and many other projects.
833 lines (719 loc) • 17.3 kB
JavaScript
/*!
* fs-utils <https://github.com/assemble/fs-utils>
*
* Copyright (c) 2014-2015 Jon Schlinkert, Brian Woodward.
* Licensed under the MIT license.
*/
;
var fs = require('graceful-fs');
var path = require('path');
var EOL = require('os').EOL;
var EOLre = new RegExp(EOL, 'g');
var utils = require('./utils');
/**
* Strip carriage returns from a string.
*
* @param {String} `str`
* @return {String}
* @api public
*/
exports.stripCR = function(str) {
return str.replace(/\r/g, '');
};
/**
* Strip byte order marks from a string.
*
* See [BOM](http://en.wikipedia.org/wiki/Byte_order_mark)
*
* @param {String} `str`
* @return {String}
* @api public
*/
exports.stripBOM = function(str) {
return str.replace(/^\uFEFF/, '');
};
/**
* Normalize all slashes to forward slashes.
*
* @param {String} `str`
* @param {Boolean} `stripTrailing` False by default.
* @return {String}
* @api public
*/
exports.slashify = function(str, trailing) {
return utils.normalize(str, trailing || false);
};
/**
* Normalize a string by stripping windows
* carriage returns and byte order marks.
*
* @param {String} `str`
* @return {String}
* @api private
*/
exports.normalize = function(str){
if (EOL !== '\n') {
str = str.replace(EOLre, '\n');
}
return exports.stripBOM(str);
};
/**
* True if the filepath actually exist.
*
* @param {String} `filepath`
* @return {Boolean}
*/
var exists = exports.exists = function(paths) {
var fp = path.join.apply(path, arguments);
try {
return fs.existsSync(fp);
} catch (err) {}
return false;
};
/**
* Return `true` if the file exists and is empty.
*
* @param {String} `filepath`
* @return {Boolean}
* @api public
*/
exports.isEmptyFile = function(fp) {
if (exists(fp) === false) {
return false;
}
var str = exports.readFileSync(fp);
return str.length > 0;
};
/**
* Return `true` if the file exists and is empty.
*
* @param {String} `filepath`
* @return {Boolean}
* @api public
*/
exports.isEmptyDir = function(fp) {
if (exists(fp) === false) {
return false;
}
var files = fs.readdirSync(fp);
return files.length > 0;
};
/**
* Return `true` if the filepath is a directory.
*
* @param {String} `filepath`
* @return {Boolean}
* @api public
*/
exports.isDir = function(filepath) {
if (!exists(filepath)) {
return false;
}
return fs.statSync(filepath)
.isDirectory();
};
/**
* True if the filepath is a file.
*
* @param {String} `filepath`
* @return {Boolean}
*/
var isFile = exports.isFile = function(filepath) {
if (!exists(filepath)) {
return false;
}
return fs.statSync(filepath)
.isFile();
};
/**
* True if the filepath is a symbolic link.
*
* @param {String} `filepath`
* @return {Boolean}
* @api public
*/
exports.isLink = function(filepath) {
return exists(filepath) && fs.lstatSync(filepath)
.isSymbolicLink();
};
/**
* Glob files using [matched]. Or glob files synchronously
* with `glob.sync`.
*
* @param {String|Array} `patterns`
* @return {options}
* @api public
*/
exports.glob = utils.glob;
/**
* Read a file synchronously. Also strips any byte order
* marks.
*
* @param {String} `filepath`
* @return {String}
* @api public
*/
exports.readFileSync = function(filepath, options) {
var opts = utils.extend({normalize: true, encoding: 'utf8'}, options);
var str = fs.readFileSync(filepath, opts.encoding);
if (opts.normalize && opts.encoding === 'utf8') {
str = exports.normalize(str);
}
return str;
};
/**
* Read a file asynchronously.
*
* @param {String} `filepath`
* @param {Object} `options`
* @param {Boolean} [options] `normalize` Strip carriage returns and BOM.
* @param {String} [options] `encoding` Default is `utf8`
* @param {Function} `callback`
* @api public
*/
var readFile = exports.readFile = function(filepath, options, cb) {
if (typeof options === 'function') {
cb = options;
options = {};
}
if (typeof cb !== 'function') {
throw new TypeError('readfile expects callback to be a function');
}
var opts = utils.extend({normalize: true, encoding: 'utf8'}, options);
fs.readFile(filepath, opts.encoding, function (err, content) {
if (err) return cb(err);
if (opts.normalize && opts.encoding === 'utf8') {
content = exports.normalize(content);
}
cb(null, content);
});
};
/**
* Read a YAML file asynchronously and parse its contents as JSON.
*
* @param {String} `filepath`
* @return {Object} `options`
* @return {Function} `cb` Callback function
* @api public
*/
exports.readYAML = function(filepath, options, cb) {
return utils.readYaml.apply(utils.readYaml, arguments);
};
/**
* Read a YAML file synchronously and parse its contents as JSON
*
* @param {String} `filepath`
* @return {Object}
* @api public
*/
exports.readYAMLSync = function(filepath, options) {
return utils.readYaml.sync.apply(utils.readYaml, arguments);
};
/**
* Read JSON file asynchronously and parse contents as JSON
*
* @param {String} `filepath`
* @param {Function} `callback`
* @return {Object}
* @api public
*/
exports.readJSON = function(filepath, cb) {
exports.readFile(filepath, function(err, contents) {
if (err) return cb(err);
cb(null, JSON.parse(contents.toString()));
});
};
/**
* Read a file synchronously and parse contents as JSON.
* marks.
*
* @param {String} `filepath`
* @return {Object}
* @api public
*/
exports.readJSONSync = function(filepath, options) {
return JSON.parse(exports.readFileSync(filepath, options));
};
/**
* Read JSON or YAML utils.async. Determins the reader automatically
* based on file extension.
*
* @param {String} `filepath`
* @param {Object} `options`
* @param {Function} `callback`
* @return {String}
* @api public
*/
exports.readData = function(filepath, options, cb) {
return utils.readData.data.apply(utils.readData, arguments);
};
/**
* Read JSON or utils.YAML. Determins the reader automatically
* based on file extension.
*
* @param {String} `filepath`
* @param {Object} `options`
* @return {String}
* @api public
*/
exports.readDataSync = function(filepath, options) {
return utils.readData.data.sync.apply(utils.readData, arguments);
};
/**
* Asynchronously create dirs and any intermediate dirs
* don't exist.
*
* @param {String} `dirpath`
*/
var mkdir = exports.mkdir = function(dest, cb) {
var dir = path.dirname(dest);
fs.exists(dir, function (exist) {
if (exist) {
fs.mkdir(dest, cb);
} else {
mkdir(dir, function (err) {
if (err) return cb(err);
fs.mkdir(dest, cb);
});
}
});
};
/**
* Synchronously create dirs and any intermediate dirs
* don't exist.
*
* @param {String} `dirpath`
*/
var mkdirSync = exports.mkdirSync = function(dirpath, mode) {
mode = mode || parseInt('0777', 8) & (~process.umask());
if (!exists(dirpath)) {
var parentDir = path.dirname(dirpath);
if (exists(parentDir)) {
fs.mkdirSync(dirpath, mode);
} else {
mkdirSync(parentDir);
fs.mkdirSync(dirpath, mode);
}
}
};
/**
* Asynchronously write a file to disk.
*
* @param {String} `dest`
* @param {String} `content`
* @param {Function} `callback`
* @api public
*/
exports.writeFile = function(dest, content, cb) {
utils.writeFile.apply(utils.writeFile, arguments);
};
/**
* Synchronously write files to disk, creating any
* intermediary directories if they don't exist.
*
* @param {String} `dest`
* @param {String} `str`
* @param {Options} `options`
* @api public
*/
exports.writeFileSync = function(dest, str, options) {
utils.writeFile.sync.apply(utils.writeFile, arguments);
};
/**
* Synchronously write JSON to disk, creating any
* intermediary directories if they don't exist.
*
* @param {String} `dest`
* @param {String} `str`
* @param {Options} `options`
* @api public
*/
exports.writeJSONSync = function(dest, str, options) {
utils.writeJson.sync.apply(utils.writeJson, arguments);
};
/**
* Asynchronously write files to disk, creating any
* intermediary directories if they don't exist.
*
* @param {String} `dest`
* @param {String} `str`
* @param {Options} `options`
* @api public
*/
exports.writeJSON = function(dest, str, options, cb) {
utils.writeJson.apply(utils.writeJson, arguments);
};
/**
* Synchronously write YAML to disk, creating any
* intermediary directories if they don't exist.
*
* @param {String} `dest`
* @param {String} `str`
* @param {Options} `options`
* @api public
*/
exports.writeYAMLSync = function(dest, str, options) {
utils.writeYaml.sync.apply(utils.writeYaml, arguments);
};
/**
* Aynchronously write YAML to disk, creating any
* intermediary directories if they don't exist.
*
* @param {String} `dest`
* @param {String} `str`
* @param {Options} `options`
* @api public
*/
exports.writeYAML = function(dest, data, options, cb) {
utils.writeYaml.apply(utils.writeYaml, arguments);
};
/**
* Synchronously write JSON or YAML to disk, creating any
* intermediary directories if they don't exist. Data
* type is determined by the `dest` file extension.
*
* ```js
* writeDataSync('foo.yml', {foo: "bar"});
* ```
*
* @param {String} `dest`
* @param {String} `str`
* @param {Options} `options`
* @api public
*/
exports.writeDataSync = function(dest, data, options) {
utils.writeData.sync.apply(utils.writeData, arguments);
};
/**
* Asynchronously write JSON or YAML to disk, creating any
* intermediary directories if they don't exist. Data
* type is determined by the `dest` file extension.
*
* ```js
* writeData('foo.yml', {foo: "bar"});
* ```
*
* @param {String} `dest`
* @param {String} `data`
* @param {Options} `options`
* @param {Function} `cb` Callback function
* @api public
*/
exports.writeData = function(dest, data, options, cb) {
utils.writeData.apply(utils.writeData, arguments);
};
/**
* Copy files synchronously;
*
* @param {String} `src`
* @param {String} `dest`
* @api public
*/
exports.copyFileSync = function(src, dest) {
exports.writeFileSync(dest, exports.readFileSync(src));
};
/**
* Asynchronously remove dirs and child dirs that exist.
*
* @param {String} `dir`
* @param {Function} `cb
* @return {Function}
* @api public
*/
exports.rmdir = function(dir, cb) {
if (typeof cb !== 'function') {
cb = function () {};
}
fs.readdir(dir, function (err, files) {
if (err) {
return cb(err);
}
utils.async.each(files, function (segment, next) {
var dir = path.join(dir, segment);
fs.stat(dir, function (err, stats) {
if (err) {
return cb(err);
}
if (stats.isDirectory()) {
utils.del(dir, next);
} else {
fs.unlink(dir, next);
}
});
}, function () {
fs.rmdir(dir, cb);
});
});
};
/**
* Delete folders and files recursively. Pass a callback
* as the last argument to use utils.async.
*
* @param {String} `patterns` Glob patterns to use.
* @param {Object} `options` Options for matched.
* @param {Function} `cb`
* @api public
*/
exports.del = function(patterns, opts, cb) {
var args = [].slice.call(arguments);
var last = args[args.length - 1];
if (typeof last === 'function') {
exports.deleteAsync(patterns, opts);
} else {
exports.deleteSync(patterns, opts);
}
};
/**
* Asynchronously delete folders and files.
*
* @param {String} `patterns` Glob patterns to use.
* @param {String} `opts` Options for matched.
* @param {Function} `cb`
* @api private
*/
exports.deleteAsync = function(patterns, opts, cb) {
if (typeof opts !== 'object') {
cb = opts;
opts = {};
}
utils.glob(patterns, opts, function (err, files) {
if (err) {
cb(err);
return;
}
utils.async.each(files, function (filepath, next) {
if (opts.cwd && !exports.isAbsolute(filepath)) {
filepath = path.resolve(opts.cwd, filepath);
}
utils.del(filepath, next);
}, cb);
});
};
/**
* Synchronously delete folders and files.
*
* @param {String} `patterns` Glob patterns to use.
* @param {Object} `options` Options for matched.
* @param {Function} `cb`
* @api private
*/
exports.deleteSync = function(patterns, options) {
var opts = utils.extend({cwd: process.cwd()}, options);
utils.glob.sync(patterns, opts).forEach(function (filepath) {
if (opts.cwd) {
filepath = path.resolve(opts.cwd, filepath);
}
utils.del.sync(filepath);
});
};
/**
* Return the file extension.
*
* @param {String} `filepath`
* @return {String}
* @api public
*/
exports.ext = function(filepath) {
return path.extname(filepath);
};
/**
* Directory path excluding filename.
*
* @param {String} `filepath`
* @return {String}
* @api public
*/
exports.dirname = function(filepath) {
return isFile(filepath)
? path.dirname(filepath)
: filepath;
};
/**
* Return an array of path segments.
*
* @param {String} `filepath`
* @return {Array}
*/
var segments = exports.segments = function(filepath) {
return filepath.split(/[\\\/]/g);
};
/**
* The last `n` segments of a filepath. If a number
* isn't passed for `n`, the last segment is returned.
*
* @param {String} `filepath`
* @return {String}
* @api public
*/
exports.last = function(filepath, num) {
var seg = segments(filepath);
return seg.slice(-(num || 1))
.join(path.sep);
};
/**
* The first `n` segments of a filepath. If a number
* isn't passed for `n`, the first segment is returned.
*
* @param {String} `filepath`
* @return {String}
* @api public
*/
exports.first = function(filepath, num) {
var seg = segments(filepath);
return seg.slice(num || 1)
.join(path.sep);
};
/**
* Returns the last character in `filepath`
*
* ```
* lastChar('foo/bar/baz/');
* //=> '/'
* ```
*
* @param {String} `filepath`
* @return {String}
* @api public
*/
exports.lastChar = function(filepath) {
var len = filepath.length;
return filepath[len - 1];
};
/**
* Remove a trailing slash from a filepath
*
* @param {String} `filepath`
* @return {String}
*/
var removeSlash = exports.removeSlash = function(filepath) {
return filepath.replace(/[\\\/]$/, '');
};
/**
* Add a trailing slash to the filepath.
*
* Note, this does _not_ consult the file system
* to check if the filepath is file or a directory.
*
* @param {String} `filepath`
* @return {String}
* @api public
*/
exports.addSlash = function(filepath) {
if (!/\./.test(path.basename(filepath))) {
return removeSlash(filepath) + path.sep;
}
return filepath;
};
/**
* Normalize a filepath and remove trailing slashes.
*
* @param {String} `filepath`
* @return {String}
* @api public
*/
exports.normalizePath = function(filepath) {
return removeSlash(path.normalize(filepath));
};
/**
* Resolve a filepath, also normalizes and removes
* trailing slashes.
*
* @param {String} `filepath`
* @return {String}
*/
var resolve = exports.resolve = function(filepath) {
var args = [].slice.call(arguments);
var paths = path.resolve.apply(path, args);
return exports.normalizePath(paths);
};
/**
* Resolve the relative path from `a` to `b.
*
* @param {String} `filepath`
* @return {String}
* @api public
*/
exports.relative = function(a, b) {
return utils.relative.apply(utils.relative, arguments);
};
/**
* Return `true` if the path is absolute.
*
* @param {[type]} filepath
* @return {Boolean}
* @api public
*/
exports.isAbsolute = function(filepath) {
return utils.isAbs.apply(utils.isAbs, arguments);
};
/**
* Return `true` if path `a` is the same as path `b.
*
* @param {String} `filepath`
* @param {String} `a`
* @param {String} `b`
* @return {Boolean}
* @api public
*/
exports.equivalent = function(a, b) {
return resolve(a) === resolve(b);
};
/**
* True if descendant path(s) contained within ancestor path.
* Note: does not test if paths actually exist.
*
* Sourced from [Grunt].
*
* @param {String} `ancestor` The starting path.
* @return {Boolean}
* @api public
*/
exports.doesPathContain = function(ancestor) {
ancestor = path.resolve(ancestor);
var args = [].slice.call(arguments, 1);
var len = arguments.length;
if (len === 0) {
return false;
}
var rel;
for (var i = 0; i < len; i++) {
rel = path.relative(resolve(args[i]), ancestor);
if (rel === '' || /\w+/.test(rel)) {
return false;
}
}
return true;
};
/**
* True if a filepath is the CWD.
*
* Sourced from [Grunt].
*
* @param {String} `filepath`
* @return {Boolean}
* @api public
*/
exports.isPathCwd = function(filepath) {
try {
var actual = fs.realpathSync(filepath);
return exports.equivalent(process.cwd(), actual);
} catch (err) {
return false;
}
};
/**
* True if a filepath is contained within the CWD.
*
* @param {String} `filepath`
* @return {Boolean}
* @api public
*/
exports.isPathInCwd = function(filepath) {
try {
var actual = fs.realpathSync(path.resolve(filepath));
console.log(actual);
return exports.doesPathContain(process.cwd(), actual);
} catch (err) {
return false;
}
};