3 * https://github.com/cowboy/node-globule
5 * Copyright (c) 2014 "Cowboy" Ben Alman
6 * Licensed under the MIT license.
11 var fs = require('fs');
12 var path = require('path');
14 var _ = require('lodash');
15 var glob = require('glob');
16 var minimatch = require('minimatch');
19 var globule = exports;
21 // Process specified wildcard glob patterns or filenames against a
22 // callback, excluding and uniquing files in the result set.
23 function processPatterns(patterns, options, fn) {
25 _.each(patterns, function(pattern) {
26 // The first character is not ! (inclusion). Add all matching filepaths
28 if (pattern.indexOf('!') !== 0) {
29 result = _.union(result, fn(pattern));
32 // The first character is ! (exclusion). Remove any filepaths from the
33 // result set that match this pattern, sans leading !.
34 var filterFn = minimatch.filter(pattern.slice(1), options);
35 result = _.filter(result, function(filepath) {
36 return !filterFn(filepath);
42 // Normalize paths to be unix-style.
43 var pathSeparatorRe = /[\/\\]/g;
44 function normalizePath(path) {
45 return path.replace(pathSeparatorRe, '/');
48 // Match a filepath or filepaths against one or more wildcard patterns. Returns
49 // all matching filepaths. This behaves just like minimatch.match, but supports
50 // any number of patterns.
51 globule.match = function(patterns, filepaths, options) {
52 // Return empty set if either patterns or filepaths was omitted.
53 if (patterns == null || filepaths == null) { return []; }
54 // Normalize patterns and filepaths to flattened arrays.
55 patterns = _.isArray(patterns) ? _.flattenDeep(patterns) : [patterns];
56 filepaths = _.isArray(filepaths) ? _.flattenDeep(filepaths) : [filepaths];
57 // Return empty set if there are no patterns or filepaths.
58 if (patterns.length === 0 || filepaths.length === 0) { return []; }
59 // Return all matching filepaths.
60 return processPatterns(patterns, options, function(pattern) {
61 return minimatch.match(filepaths, pattern, options || {});
65 // Match a filepath or filepaths against one or more wildcard patterns. Returns
66 // true if any of the patterns match.
67 globule.isMatch = function() {
68 return globule.match.apply(null, arguments).length > 0;
71 // Return an array of all file paths that match the given wildcard patterns.
72 globule.find = function() {
73 var args = _.toArray(arguments);
74 // If the last argument is an options object, remove it from args.
75 var options = _.isPlainObject(args[args.length - 1]) ? args.pop() : {};
76 // If options.src was specified, use it. Otherwise, use all non-options
77 // arguments. Flatten nested arrays.
80 patterns = _.isArray(options.src) ? _.flattenDeep(options.src) : [options.src];
82 patterns = _.flattenDeep(args);
84 // Return empty set if there are no patterns.
85 if (patterns.length === 0) { return []; }
86 var srcBase = options.srcBase || options.cwd;
87 // Create glob-specific options object.
88 var globOptions = _.extend({}, options);
90 globOptions.cwd = srcBase;
92 // Get all matching filepaths.
93 var matches = processPatterns(patterns, options, function(pattern) {
94 return glob.sync(pattern, globOptions);
96 // If srcBase and prefixBase were specified, prefix srcBase to matched paths.
97 if (srcBase && options.prefixBase) {
98 matches = matches.map(function(filepath) {
99 return normalizePath(path.join(srcBase, filepath));
102 // Filter result set?
103 if (options.filter) {
104 matches = matches.filter(function(filepath) {
105 // If srcBase was specified but prefixBase was NOT, prefix srcBase
106 // temporarily, for filtering.
107 if (srcBase && !options.prefixBase) {
108 filepath = normalizePath(path.join(srcBase, filepath));
111 if (_.isFunction(options.filter)) {
112 return options.filter(filepath, options);
114 // If the file is of the right type and exists, this should work.
115 return fs.statSync(filepath)[options.filter]();
118 // Otherwise, it's probably not the right type.
127 first: /(\.[^\/]*)?$/,
128 last: /(\.[^\/\.]*)?$/,
130 function rename(dest, options) {
132 if (options.flatten) {
133 dest = path.basename(dest);
135 // Change the extension?
137 dest = dest.replace(extDotRe[options.extDot], options.ext);
139 // Join dest and destBase?
140 if (options.destBase) {
141 dest = path.join(options.destBase, dest);
146 // Build a mapping of src-dest filepaths from the given set of filepaths.
147 globule.mapping = function(filepaths, options) {
148 // Return empty set if filepaths was omitted.
149 if (filepaths == null) { return []; }
150 options = _.defaults({}, options, {
156 // Find all files matching pattern, using passed-in options.
157 filepaths.forEach(function(src) {
158 // Generate destination filename.
159 var dest = options.rename(src, options);
160 // Prepend srcBase to all src paths.
161 if (options.srcBase) {
162 src = path.join(options.srcBase, src);
164 // Normalize filepaths to be unix-style.
165 dest = normalizePath(dest);
166 src = normalizePath(src);
167 // Map correct src path to dest path.
168 if (fileByDest[dest]) {
169 // If dest already exists, push this src onto that dest's src array.
170 fileByDest[dest].src.push(src);
172 // Otherwise create a new src-dest file mapping object.
177 // And store a reference for later use.
178 fileByDest[dest] = files[files.length - 1];
184 // Return a mapping of src-dest filepaths from files matching the given
185 // wildcard patterns.
186 globule.findMapping = function() {
187 var args = _.toArray(arguments);
188 // If the last argument is an options object, remove it from args.
189 var options = _.isPlainObject(args[args.length - 1]) ? args.pop() : {};
190 // Generate mapping from found filepaths.
191 return globule.mapping(globule.find(args, options), options);