var grunt = require('../grunt');
var path = require('path');
var file = module.exports = {};
file.glob = require('glob');
file.minimatch = require('minimatch');
file.findup = require('findup-sync');
var YAML = require('js-yaml');
var rimraf = require('rimraf');
var iconv = require('iconv-lite');
var win32 = process.platform === 'win32';
var unixifyPath = function(filepath) {
return filepath.replace(/\\/g, '/');
file.setBase = function() {
var dirpath = path.join.apply(path, arguments);
var processPatterns = function(patterns, fn) {
grunt.util._.flatten(patterns).forEach(function(pattern) {
var exclusion = pattern.indexOf('!') === 0;
if (exclusion) { pattern = pattern.slice(1); }
var matches = fn(pattern);
result = grunt.util._.difference(result, matches);
result = grunt.util._.union(result, matches);
file.match = function(options, patterns, filepaths) {
if (grunt.util.kindOf(options) !== 'object') {
if (patterns == null || filepaths == null) { return []; }
if (!Array.isArray(patterns)) { patterns = [patterns]; }
if (!Array.isArray(filepaths)) { filepaths = [filepaths]; }
if (patterns.length === 0 || filepaths.length === 0) { return []; }
return processPatterns(patterns, function(pattern) {
return file.minimatch.match(filepaths, pattern, options);
file.isMatch = function() {
return file.match.apply(file, arguments).length > 0;
file.expand = function() {
var args = grunt.util.toArray(arguments);
var options = grunt.util.kindOf(args[0]) === 'object' ? args.shift() : {};
var patterns = Array.isArray(args[0]) ? args[0] : args;
if (patterns.length === 0) { return []; }
var matches = processPatterns(patterns, function(pattern) {
return file.glob.sync(pattern, options);
matches = matches.filter(function(filepath) {
filepath = path.join(options.cwd || '', filepath);
if (typeof options.filter === 'function') {
return options.filter(filepath);
return fs.statSync(filepath)[options.filter]();
var pathSeparatorRe = /[\/\\]/g;
file.expandMapping = function(patterns, destBase, options) {
options = grunt.util._.defaults({}, options, {
rename: function(destBase, destPath) {
return path.join(destBase || '', destPath);
file.expand(options, patterns).forEach(function(src) {
destPath = path.basename(destPath);
destPath = destPath.replace(/(\.[^\/]*)?$/, options.ext);
var dest = options.rename(destBase, destPath, options);
if (options.cwd) { src = path.join(options.cwd, src); }
dest = dest.replace(pathSeparatorRe, '/');
src = src.replace(pathSeparatorRe, '/');
fileByDest[dest].src.push(src);
fileByDest[dest] = files[files.length - 1];
file.mkdir = function(dirpath, mode) {
if (grunt.option('no-write')) { return; }
mode = parseInt('0777', 8) & (~process.umask());
dirpath.split(pathSeparatorRe).reduce(function(parts, part) {
var subpath = path.resolve(parts);
if (!file.exists(subpath)) {
fs.mkdirSync(subpath, mode);
throw grunt.util.error('Unable to create directory "' + subpath + '" (Error code: ' + e.code + ').', e);
file.recurse = function recurse(rootdir, callback, subdir) {
var abspath = subdir ? path.join(rootdir, subdir) : rootdir;
fs.readdirSync(abspath).forEach(function(filename) {
var filepath = path.join(abspath, filename);
if (fs.statSync(filepath).isDirectory()) {
recurse(rootdir, callback, unixifyPath(path.join(subdir || '', filename || '')));
callback(unixifyPath(filepath), rootdir, subdir, filename);
file.defaultEncoding = 'utf8';
file.read = function(filepath, options) {
if (!options) { options = {}; }
grunt.verbose.write('Reading ' + filepath + '...');
contents = fs.readFileSync(String(filepath));
if (options.encoding !== null) {
contents = iconv.decode(contents, options.encoding || file.defaultEncoding);
if (contents.charCodeAt(0) === 0xFEFF) {
contents = contents.substring(1);
throw grunt.util.error('Unable to read "' + filepath + '" file (Error code: ' + e.code + ').', e);
file.readJSON = function(filepath, options) {
var src = file.read(filepath, options);
grunt.verbose.write('Parsing ' + filepath + '...');
result = JSON.parse(src);
throw grunt.util.error('Unable to parse "' + filepath + '" file (' + e.message + ').', e);
file.readYAML = function(filepath, options) {
var src = file.read(filepath, options);
grunt.verbose.write('Parsing ' + filepath + '...');
throw grunt.util.error('Unable to parse "' + filepath + '" file (' + e.problem + ').', e);
file.write = function(filepath, contents, options) {
if (!options) { options = {}; }
var nowrite = grunt.option('no-write');
grunt.verbose.write((nowrite ? 'Not actually writing ' : 'Writing ') + filepath + '...');
file.mkdir(path.dirname(filepath));
if (!Buffer.isBuffer(contents)) {
contents = iconv.encode(contents, options.encoding || file.defaultEncoding);
fs.writeFileSync(filepath, contents);
throw grunt.util.error('Unable to write "' + filepath + '" file (Error code: ' + e.code + ').', e);
file.copy = function(srcpath, destpath, options) {
if (!options) { options = {}; }
var process = options.process && options.noProcess !== true &&
!(options.noProcess && file.isMatch(options.noProcess, srcpath));
var readWriteOptions = process ? options : {encoding: null};
var contents = file.read(srcpath, readWriteOptions);
grunt.verbose.write('Processing source...');
contents = options.process(contents, srcpath);
throw grunt.util.error('Error while processing "' + srcpath + '" file.', e);
if (contents === false) {
grunt.verbose.writeln('Write aborted.');
file.write(destpath, contents, readWriteOptions);
file.delete = function(filepath, options) {
filepath = String(filepath);
var nowrite = grunt.option('no-write');
options = {force: grunt.option('force') || false};
grunt.verbose.write((nowrite ? 'Not actually deleting ' : 'Deleting ') + filepath + '...');
if (!file.exists(filepath)) {
grunt.log.warn('Cannot delete nonexistent file.');
if (file.isPathCwd(filepath)) {
grunt.fail.warn('Cannot delete the current working directory.');
} else if (!file.isPathInCwd(filepath)) {
grunt.fail.warn('Cannot delete files outside the current working directory.');
throw grunt.util.error('Unable to delete "' + filepath + '" file (' + e.message + ').', e);
file.exists = function() {
var filepath = path.join.apply(path, arguments);
return fs.existsSync(filepath);
file.isLink = function() {
var filepath = path.join.apply(path, arguments);
return file.exists(filepath) && fs.lstatSync(filepath).isSymbolicLink();
file.isDir = function() {
var filepath = path.join.apply(path, arguments);
return file.exists(filepath) && fs.statSync(filepath).isDirectory();
file.isFile = function() {
var filepath = path.join.apply(path, arguments);
return file.exists(filepath) && fs.statSync(filepath).isFile();
file.isPathAbsolute = function() {
var filepath = path.join.apply(path, arguments);
return path.resolve(filepath) === filepath.replace(/[\/\\]+$/, '');
file.arePathsEquivalent = function(first) {
first = path.resolve(first);
for (var i = 1; i < arguments.length; i++) {
if (first !== path.resolve(arguments[i])) { return false; }
file.doesPathContain = function(ancestor) {
ancestor = path.resolve(ancestor);
for (var i = 1; i < arguments.length; i++) {
relative = path.relative(path.resolve(arguments[i]), ancestor);
if (relative === '' || /\w+/.test(relative)) { return false; }
file.isPathCwd = function() {
var filepath = path.join.apply(path, arguments);
return file.arePathsEquivalent(process.cwd(), fs.realpathSync(filepath));
file.isPathInCwd = function() {
var filepath = path.join.apply(path, arguments);
return file.doesPathContain(process.cwd(), fs.realpathSync(filepath));