/** | |
* node-archiver | |
* | |
* Copyright (c) 2012-2014 Chris Talkington, contributors. | |
* Licensed under the MIT license. | |
* https://github.com/ctalkington/node-archiver/blob/master/LICENSE-MIT | |
*/ | |
var fs = require('fs'); | |
var path = require('path'); | |
var _ = require('lodash'); | |
var glob = require('glob'); | |
var file = module.exports = {}; | |
var pathSeparatorRe = /[\/\\]/g; | |
// Process specified wildcard glob patterns or filenames against a | |
// callback, excluding and uniquing files in the result set. | |
var processPatterns = function(patterns, fn) { | |
// Filepaths to return. | |
var result = []; | |
// Iterate over flattened patterns array. | |
_.flatten(patterns).forEach(function(pattern) { | |
// If the first character is ! it should be omitted | |
var exclusion = pattern.indexOf('!') === 0; | |
// If the pattern is an exclusion, remove the ! | |
if (exclusion) { pattern = pattern.slice(1); } | |
// Find all matching files for this pattern. | |
var matches = fn(pattern); | |
if (exclusion) { | |
// If an exclusion, remove matching files. | |
result = _.difference(result, matches); | |
} else { | |
// Otherwise add matching files. | |
result = _.union(result, matches); | |
} | |
}); | |
return result; | |
}; | |
// True if the file path exists. | |
file.exists = function() { | |
var filepath = path.join.apply(path, arguments); | |
return fs.existsSync(filepath); | |
}; | |
// Return an array of all file paths that match the given wildcard patterns. | |
file.expand = function() { | |
var args = _.toArray(arguments); | |
// If the first argument is an options object, save those options to pass | |
// into the File.prototype.glob.sync method. | |
var options = _.isPlainObject(args[0]) ? args.shift() : {}; | |
// Use the first argument if it's an Array, otherwise convert the arguments | |
// object to an array and use that. | |
var patterns = Array.isArray(args[0]) ? args[0] : args; | |
// Return empty set if there are no patterns or filepaths. | |
if (patterns.length === 0) { return []; } | |
// Return all matching filepaths. | |
var matches = processPatterns(patterns, function(pattern) { | |
// Find all matching files for this pattern. | |
return glob.sync(pattern, options); | |
}); | |
// Filter result set? | |
if (options.filter) { | |
matches = matches.filter(function(filepath) { | |
filepath = path.join(options.cwd || '', filepath); | |
try { | |
if (typeof options.filter === 'function') { | |
return options.filter(filepath); | |
} else { | |
// If the file is of the right type and exists, this should work. | |
return fs.statSync(filepath)[options.filter](); | |
} | |
} catch(e) { | |
// Otherwise, it's probably not the right type. | |
return false; | |
} | |
}); | |
} | |
return matches; | |
}; | |
// Build a multi task "files" object dynamically. | |
file.expandMapping = function(patterns, destBase, options) { | |
options = _.defaults({}, options, { | |
rename: function(destBase, destPath) { | |
return path.join(destBase || '', destPath); | |
} | |
}); | |
var files = []; | |
var fileByDest = {}; | |
// Find all files matching pattern, using passed-in options. | |
file.expand(options, patterns).forEach(function(src) { | |
var destPath = src; | |
// Flatten? | |
if (options.flatten) { | |
destPath = path.basename(destPath); | |
} | |
// Change the extension? | |
if (options.ext) { | |
destPath = destPath.replace(/(\.[^\/]*)?$/, options.ext); | |
} | |
// Generate destination filename. | |
var dest = options.rename(destBase, destPath, options); | |
// Prepend cwd to src path if necessary. | |
if (options.cwd) { src = path.join(options.cwd, src); } | |
// Normalize filepaths to be unix-style. | |
dest = dest.replace(pathSeparatorRe, '/'); | |
src = src.replace(pathSeparatorRe, '/'); | |
// Map correct src path to dest path. | |
if (fileByDest[dest]) { | |
// If dest already exists, push this src onto that dest's src array. | |
fileByDest[dest].src.push(src); | |
} else { | |
// Otherwise create a new src-dest file mapping object. | |
files.push({ | |
src: [src], | |
dest: dest, | |
}); | |
// And store a reference for later use. | |
fileByDest[dest] = files[files.length - 1]; | |
} | |
}); | |
return files; | |
}; | |
// reusing bits of grunt's multi-task source normalization | |
file.normalizeFilesArray = function(data) { | |
var files = []; | |
data.forEach(function(obj) { | |
var prop; | |
if ('src' in obj || 'dest' in obj) { | |
files.push(obj); | |
} | |
}); | |
if (files.length === 0) { | |
return []; | |
} | |
files = _(files).chain().forEach(function(obj) { | |
if (!('src' in obj) || !obj.src) { return; } | |
// Normalize .src properties to flattened array. | |
if (Array.isArray(obj.src)) { | |
obj.src = _.flatten(obj.src); | |
} else { | |
obj.src = [obj.src]; | |
} | |
}).map(function(obj) { | |
// Build options object, removing unwanted properties. | |
var expandOptions = _.extend({}, obj); | |
delete expandOptions.src; | |
delete expandOptions.dest; | |
// Expand file mappings. | |
if (obj.expand) { | |
return file.expandMapping(obj.src, obj.dest, expandOptions).map(function(mapObj) { | |
// Copy obj properties to result. | |
var result = _.extend({}, obj); | |
// Make a clone of the orig obj available. | |
result.orig = _.extend({}, obj); | |
// Set .src and .dest, processing both as templates. | |
result.src = mapObj.src; | |
result.dest = mapObj.dest; | |
// Remove unwanted properties. | |
['expand', 'cwd', 'flatten', 'rename', 'ext'].forEach(function(prop) { | |
delete result[prop]; | |
}); | |
return result; | |
}); | |
} | |
// Copy obj properties to result, adding an .orig property. | |
var result = _.extend({}, obj); | |
// Make a clone of the orig obj available. | |
result.orig = _.extend({}, obj); | |
if ('src' in result) { | |
// Expose an expand-on-demand getter method as .src. | |
Object.defineProperty(result, 'src', { | |
enumerable: true, | |
get: function fn() { | |
var src; | |
if (!('result' in fn)) { | |
src = obj.src; | |
// If src is an array, flatten it. Otherwise, make it into an array. | |
src = Array.isArray(src) ? _.flatten(src) : [src]; | |
// Expand src files, memoizing result. | |
fn.result = file.expand(expandOptions, src); | |
} | |
return fn.result; | |
} | |
}); | |
} | |
if ('dest' in result) { | |
result.dest = obj.dest; | |
} | |
return result; | |
}).flatten().value(); | |
return files; | |
}; |