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  };