file.js
1 /** 2 * archiver-utils 3 * 4 * Copyright (c) 2012-2014 Chris Talkington, contributors. 5 * Licensed under the MIT license. 6 * https://github.com/archiverjs/node-archiver/blob/master/LICENSE-MIT 7 */ 8 var fs = require('graceful-fs'); 9 var path = require('path'); 10 11 var flatten = require('lodash.flatten'); 12 var difference = require('lodash.difference'); 13 var union = require('lodash.union'); 14 var isPlainObject = require('lodash.isplainobject'); 15 16 var glob = require('glob'); 17 18 var file = module.exports = {}; 19 20 var pathSeparatorRe = /[\/\\]/g; 21 22 // Process specified wildcard glob patterns or filenames against a 23 // callback, excluding and uniquing files in the result set. 24 var processPatterns = function(patterns, fn) { 25 // Filepaths to return. 26 var result = []; 27 // Iterate over flattened patterns array. 28 flatten(patterns).forEach(function(pattern) { 29 // If the first character is ! it should be omitted 30 var exclusion = pattern.indexOf('!') === 0; 31 // If the pattern is an exclusion, remove the ! 32 if (exclusion) { pattern = pattern.slice(1); } 33 // Find all matching files for this pattern. 34 var matches = fn(pattern); 35 if (exclusion) { 36 // If an exclusion, remove matching files. 37 result = difference(result, matches); 38 } else { 39 // Otherwise add matching files. 40 result = union(result, matches); 41 } 42 }); 43 return result; 44 }; 45 46 // True if the file path exists. 47 file.exists = function() { 48 var filepath = path.join.apply(path, arguments); 49 return fs.existsSync(filepath); 50 }; 51 52 // Return an array of all file paths that match the given wildcard patterns. 53 file.expand = function(...args) { 54 // If the first argument is an options object, save those options to pass 55 // into the File.prototype.glob.sync method. 56 var options = isPlainObject(args[0]) ? args.shift() : {}; 57 // Use the first argument if it's an Array, otherwise convert the arguments 58 // object to an array and use that. 59 var patterns = Array.isArray(args[0]) ? args[0] : args; 60 // Return empty set if there are no patterns or filepaths. 61 if (patterns.length === 0) { return []; } 62 // Return all matching filepaths. 63 var matches = processPatterns(patterns, function(pattern) { 64 // Find all matching files for this pattern. 65 return glob.sync(pattern, options); 66 }); 67 // Filter result set? 68 if (options.filter) { 69 matches = matches.filter(function(filepath) { 70 filepath = path.join(options.cwd || '', filepath); 71 try { 72 if (typeof options.filter === 'function') { 73 return options.filter(filepath); 74 } else { 75 // If the file is of the right type and exists, this should work. 76 return fs.statSync(filepath)[options.filter](); 77 } 78 } catch(e) { 79 // Otherwise, it's probably not the right type. 80 return false; 81 } 82 }); 83 } 84 return matches; 85 }; 86 87 // Build a multi task "files" object dynamically. 88 file.expandMapping = function(patterns, destBase, options) { 89 options = Object.assign({ 90 rename: function(destBase, destPath) { 91 return path.join(destBase || '', destPath); 92 } 93 }, options); 94 var files = []; 95 var fileByDest = {}; 96 // Find all files matching pattern, using passed-in options. 97 file.expand(options, patterns).forEach(function(src) { 98 var destPath = src; 99 // Flatten? 100 if (options.flatten) { 101 destPath = path.basename(destPath); 102 } 103 // Change the extension? 104 if (options.ext) { 105 destPath = destPath.replace(/(\.[^\/]*)?$/, options.ext); 106 } 107 // Generate destination filename. 108 var dest = options.rename(destBase, destPath, options); 109 // Prepend cwd to src path if necessary. 110 if (options.cwd) { src = path.join(options.cwd, src); } 111 // Normalize filepaths to be unix-style. 112 dest = dest.replace(pathSeparatorRe, '/'); 113 src = src.replace(pathSeparatorRe, '/'); 114 // Map correct src path to dest path. 115 if (fileByDest[dest]) { 116 // If dest already exists, push this src onto that dest's src array. 117 fileByDest[dest].src.push(src); 118 } else { 119 // Otherwise create a new src-dest file mapping object. 120 files.push({ 121 src: [src], 122 dest: dest, 123 }); 124 // And store a reference for later use. 125 fileByDest[dest] = files[files.length - 1]; 126 } 127 }); 128 return files; 129 }; 130 131 // reusing bits of grunt's multi-task source normalization 132 file.normalizeFilesArray = function(data) { 133 var files = []; 134 135 data.forEach(function(obj) { 136 var prop; 137 if ('src' in obj || 'dest' in obj) { 138 files.push(obj); 139 } 140 }); 141 142 if (files.length === 0) { 143 return []; 144 } 145 146 files = _(files).chain().forEach(function(obj) { 147 if (!('src' in obj) || !obj.src) { return; } 148 // Normalize .src properties to flattened array. 149 if (Array.isArray(obj.src)) { 150 obj.src = flatten(obj.src); 151 } else { 152 obj.src = [obj.src]; 153 } 154 }).map(function(obj) { 155 // Build options object, removing unwanted properties. 156 var expandOptions = Object.assign({}, obj); 157 delete expandOptions.src; 158 delete expandOptions.dest; 159 160 // Expand file mappings. 161 if (obj.expand) { 162 return file.expandMapping(obj.src, obj.dest, expandOptions).map(function(mapObj) { 163 // Copy obj properties to result. 164 var result = Object.assign({}, obj); 165 // Make a clone of the orig obj available. 166 result.orig = Object.assign({}, obj); 167 // Set .src and .dest, processing both as templates. 168 result.src = mapObj.src; 169 result.dest = mapObj.dest; 170 // Remove unwanted properties. 171 ['expand', 'cwd', 'flatten', 'rename', 'ext'].forEach(function(prop) { 172 delete result[prop]; 173 }); 174 return result; 175 }); 176 } 177 178 // Copy obj properties to result, adding an .orig property. 179 var result = Object.assign({}, obj); 180 // Make a clone of the orig obj available. 181 result.orig = Object.assign({}, obj); 182 183 if ('src' in result) { 184 // Expose an expand-on-demand getter method as .src. 185 Object.defineProperty(result, 'src', { 186 enumerable: true, 187 get: function fn() { 188 var src; 189 if (!('result' in fn)) { 190 src = obj.src; 191 // If src is an array, flatten it. Otherwise, make it into an array. 192 src = Array.isArray(src) ? flatten(src) : [src]; 193 // Expand src files, memoizing result. 194 fn.result = file.expand(expandOptions, src); 195 } 196 return fn.result; 197 } 198 }); 199 } 200 201 if ('dest' in result) { 202 result.dest = obj.dest; 203 } 204 205 return result; 206 }).flatten().value(); 207 208 return files; 209 };