303e0ab406ebc50b39f7fc9204aad48bf7f5a66e
[yaffs-website] / node_modules / grunt / lib / grunt / file.js
1 'use strict';
2
3 var grunt = require('../grunt');
4
5 // Nodejs libs.
6 var fs = require('fs');
7 var path = require('path');
8
9 // The module to be exported.
10 var file = module.exports = {};
11
12 // External libs.
13 file.glob = require('glob');
14 file.minimatch = require('minimatch');
15 file.findup = require('findup-sync');
16 var YAML = require('js-yaml');
17 var rimraf = require('rimraf');
18 var iconv = require('iconv-lite');
19 var pathIsAbsolute = require('path-is-absolute');
20
21 // Windows?
22 var win32 = process.platform === 'win32';
23
24 // Normalize \\ paths to / paths.
25 var unixifyPath = function(filepath) {
26   if (win32) {
27     return filepath.replace(/\\/g, '/');
28   } else {
29     return filepath;
30   }
31 };
32
33 // Change the current base path (ie, CWD) to the specified path.
34 file.setBase = function() {
35   var dirpath = path.join.apply(path, arguments);
36   process.chdir(dirpath);
37 };
38
39 // Process specified wildcard glob patterns or filenames against a
40 // callback, excluding and uniquing files in the result set.
41 var processPatterns = function(patterns, fn) {
42   // Filepaths to return.
43   var result = [];
44   // Iterate over flattened patterns array.
45   grunt.util._.flattenDeep(patterns).forEach(function(pattern) {
46     // If the first character is ! it should be omitted
47     var exclusion = pattern.indexOf('!') === 0;
48     // If the pattern is an exclusion, remove the !
49     if (exclusion) { pattern = pattern.slice(1); }
50     // Find all matching files for this pattern.
51     var matches = fn(pattern);
52     if (exclusion) {
53       // If an exclusion, remove matching files.
54       result = grunt.util._.difference(result, matches);
55     } else {
56       // Otherwise add matching files.
57       result = grunt.util._.union(result, matches);
58     }
59   });
60   return result;
61 };
62
63 // Match a filepath or filepaths against one or more wildcard patterns. Returns
64 // all matching filepaths.
65 file.match = function(options, patterns, filepaths) {
66   if (grunt.util.kindOf(options) !== 'object') {
67     filepaths = patterns;
68     patterns = options;
69     options = {};
70   }
71   // Return empty set if either patterns or filepaths was omitted.
72   if (patterns == null || filepaths == null) { return []; }
73   // Normalize patterns and filepaths to arrays.
74   if (!Array.isArray(patterns)) { patterns = [patterns]; }
75   if (!Array.isArray(filepaths)) { filepaths = [filepaths]; }
76   // Return empty set if there are no patterns or filepaths.
77   if (patterns.length === 0 || filepaths.length === 0) { return []; }
78   // Return all matching filepaths.
79   return processPatterns(patterns, function(pattern) {
80     return file.minimatch.match(filepaths, pattern, options);
81   });
82 };
83
84 // Match a filepath or filepaths against one or more wildcard patterns. Returns
85 // true if any of the patterns match.
86 file.isMatch = function() {
87   return file.match.apply(file, arguments).length > 0;
88 };
89
90 // Return an array of all file paths that match the given wildcard patterns.
91 file.expand = function() {
92   var args = grunt.util.toArray(arguments);
93   // If the first argument is an options object, save those options to pass
94   // into the file.glob.sync method.
95   var options = grunt.util.kindOf(args[0]) === 'object' ? args.shift() : {};
96   // Use the first argument if it's an Array, otherwise convert the arguments
97   // object to an array and use that.
98   var patterns = Array.isArray(args[0]) ? args[0] : args;
99   // Return empty set if there are no patterns or filepaths.
100   if (patterns.length === 0) { return []; }
101   // Return all matching filepaths.
102   var matches = processPatterns(patterns, function(pattern) {
103     // Find all matching files for this pattern.
104     return file.glob.sync(pattern, options);
105   });
106   // Filter result set?
107   if (options.filter) {
108     matches = matches.filter(function(filepath) {
109       filepath = path.join(options.cwd || '', filepath);
110       try {
111         if (typeof options.filter === 'function') {
112           return options.filter(filepath);
113         } else {
114           // If the file is of the right type and exists, this should work.
115           return fs.statSync(filepath)[options.filter]();
116         }
117       } catch (e) {
118         // Otherwise, it's probably not the right type.
119         return false;
120       }
121     });
122   }
123   return matches;
124 };
125
126 var pathSeparatorRe = /[\/\\]/g;
127
128 // The "ext" option refers to either everything after the first dot (default)
129 // or everything after the last dot.
130 var extDotRe = {
131   first: /(\.[^\/]*)?$/,
132   last: /(\.[^\/\.]*)?$/,
133 };
134
135 // Build a multi task "files" object dynamically.
136 file.expandMapping = function(patterns, destBase, options) {
137   options = grunt.util._.defaults({}, options, {
138     extDot: 'first',
139     rename: function(destBase, destPath) {
140       return path.join(destBase || '', destPath);
141     }
142   });
143   var files = [];
144   var fileByDest = {};
145   // Find all files matching pattern, using passed-in options.
146   file.expand(options, patterns).forEach(function(src) {
147     var destPath = src;
148     // Flatten?
149     if (options.flatten) {
150       destPath = path.basename(destPath);
151     }
152     // Change the extension?
153     if ('ext' in options) {
154       destPath = destPath.replace(extDotRe[options.extDot], options.ext);
155     }
156     // Generate destination filename.
157     var dest = options.rename(destBase, destPath, options);
158     // Prepend cwd to src path if necessary.
159     if (options.cwd) { src = path.join(options.cwd, src); }
160     // Normalize filepaths to be unix-style.
161     dest = dest.replace(pathSeparatorRe, '/');
162     src = src.replace(pathSeparatorRe, '/');
163     // Map correct src path to dest path.
164     if (fileByDest[dest]) {
165       // If dest already exists, push this src onto that dest's src array.
166       fileByDest[dest].src.push(src);
167     } else {
168       // Otherwise create a new src-dest file mapping object.
169       files.push({
170         src: [src],
171         dest: dest,
172       });
173       // And store a reference for later use.
174       fileByDest[dest] = files[files.length - 1];
175     }
176   });
177   return files;
178 };
179
180 // Like mkdir -p. Create a directory and any intermediary directories.
181 file.mkdir = function(dirpath, mode) {
182   if (grunt.option('no-write')) { return; }
183   // Set directory mode in a strict-mode-friendly way.
184   if (mode == null) {
185     mode = parseInt('0777', 8) & (~process.umask());
186   }
187   dirpath.split(pathSeparatorRe).reduce(function(parts, part) {
188     parts += part + '/';
189     var subpath = path.resolve(parts);
190     if (!file.exists(subpath)) {
191       try {
192         fs.mkdirSync(subpath, mode);
193       } catch (e) {
194         throw grunt.util.error('Unable to create directory "' + subpath + '" (Error code: ' + e.code + ').', e);
195       }
196     }
197     return parts;
198   }, '');
199 };
200
201 // Recurse into a directory, executing callback for each file.
202 file.recurse = function recurse(rootdir, callback, subdir) {
203   var abspath = subdir ? path.join(rootdir, subdir) : rootdir;
204   fs.readdirSync(abspath).forEach(function(filename) {
205     var filepath = path.join(abspath, filename);
206     if (fs.statSync(filepath).isDirectory()) {
207       recurse(rootdir, callback, unixifyPath(path.join(subdir || '', filename || '')));
208     } else {
209       callback(unixifyPath(filepath), rootdir, subdir, filename);
210     }
211   });
212 };
213
214 // The default file encoding to use.
215 file.defaultEncoding = 'utf8';
216 // Whether to preserve the BOM on file.read rather than strip it.
217 file.preserveBOM = false;
218
219 // Read a file, return its contents.
220 file.read = function(filepath, options) {
221   if (!options) { options = {}; }
222   var contents;
223   grunt.verbose.write('Reading ' + filepath + '...');
224   try {
225     contents = fs.readFileSync(String(filepath));
226     // If encoding is not explicitly null, convert from encoded buffer to a
227     // string. If no encoding was specified, use the default.
228     if (options.encoding !== null) {
229       contents = iconv.decode(contents, options.encoding || file.defaultEncoding, {stripBOM: !file.preserveBOM});
230     }
231     grunt.verbose.ok();
232     return contents;
233   } catch (e) {
234     grunt.verbose.error();
235     throw grunt.util.error('Unable to read "' + filepath + '" file (Error code: ' + e.code + ').', e);
236   }
237 };
238
239 // Read a file, parse its contents, return an object.
240 file.readJSON = function(filepath, options) {
241   var src = file.read(filepath, options);
242   var result;
243   grunt.verbose.write('Parsing ' + filepath + '...');
244   try {
245     result = JSON.parse(src);
246     grunt.verbose.ok();
247     return result;
248   } catch (e) {
249     grunt.verbose.error();
250     throw grunt.util.error('Unable to parse "' + filepath + '" file (' + e.message + ').', e);
251   }
252 };
253
254 // Read a YAML file, parse its contents, return an object.
255 file.readYAML = function(filepath, options) {
256   var src = file.read(filepath, options);
257   var result;
258   grunt.verbose.write('Parsing ' + filepath + '...');
259   try {
260     result = YAML.load(src);
261     grunt.verbose.ok();
262     return result;
263   } catch (e) {
264     grunt.verbose.error();
265     throw grunt.util.error('Unable to parse "' + filepath + '" file (' + e.problem + ').', e);
266   }
267 };
268
269 // Write a file.
270 file.write = function(filepath, contents, options) {
271   if (!options) { options = {}; }
272   var nowrite = grunt.option('no-write');
273   grunt.verbose.write((nowrite ? 'Not actually writing ' : 'Writing ') + filepath + '...');
274   // Create path, if necessary.
275   file.mkdir(path.dirname(filepath));
276   try {
277     // If contents is already a Buffer, don't try to encode it. If no encoding
278     // was specified, use the default.
279     if (!Buffer.isBuffer(contents)) {
280       contents = iconv.encode(contents, options.encoding || file.defaultEncoding);
281     }
282     // Actually write file.
283     if (!nowrite) {
284       fs.writeFileSync(filepath, contents, 'mode' in options ? {mode: options.mode} : {});
285     }
286     grunt.verbose.ok();
287     return true;
288   } catch (e) {
289     grunt.verbose.error();
290     throw grunt.util.error('Unable to write "' + filepath + '" file (Error code: ' + e.code + ').', e);
291   }
292 };
293
294 // Read a file, optionally processing its content, then write the output.
295 // Or read a directory, recursively creating directories, reading files,
296 // processing content, writing output.
297 file.copy = function copy(srcpath, destpath, options) {
298   if (file.isDir(srcpath)) {
299     // Copy a directory, recursively.
300     // Explicitly create new dest directory.
301     file.mkdir(destpath);
302     // Iterate over all sub-files/dirs, recursing.
303     fs.readdirSync(srcpath).forEach(function(filepath) {
304       copy(path.join(srcpath, filepath), path.join(destpath, filepath), options);
305     });
306   } else {
307     // Copy a single file.
308     file._copy(srcpath, destpath, options);
309   }
310 };
311
312 // Read a file, optionally processing its content, then write the output.
313 file._copy = function(srcpath, destpath, options) {
314   if (!options) { options = {}; }
315   // If a process function was specified, and noProcess isn't true or doesn't
316   // match the srcpath, process the file's source.
317   var process = options.process && options.noProcess !== true &&
318     !(options.noProcess && file.isMatch(options.noProcess, srcpath));
319   // If the file will be processed, use the encoding as-specified. Otherwise,
320   // use an encoding of null to force the file to be read/written as a Buffer.
321   var readWriteOptions = process ? options : {encoding: null};
322   // Actually read the file.
323   var contents = file.read(srcpath, readWriteOptions);
324   if (process) {
325     grunt.verbose.write('Processing source...');
326     try {
327       contents = options.process(contents, srcpath, destpath);
328       grunt.verbose.ok();
329     } catch (e) {
330       grunt.verbose.error();
331       throw grunt.util.error('Error while processing "' + srcpath + '" file.', e);
332     }
333   }
334   // Abort copy if the process function returns false.
335   if (contents === false) {
336     grunt.verbose.writeln('Write aborted.');
337   } else {
338     file.write(destpath, contents, readWriteOptions);
339   }
340 };
341
342 // Delete folders and files recursively
343 file.delete = function(filepath, options) {
344   filepath = String(filepath);
345
346   var nowrite = grunt.option('no-write');
347   if (!options) {
348     options = {force: grunt.option('force') || false};
349   }
350
351   grunt.verbose.write((nowrite ? 'Not actually deleting ' : 'Deleting ') + filepath + '...');
352
353   if (!file.exists(filepath)) {
354     grunt.verbose.error();
355     grunt.log.warn('Cannot delete nonexistent file.');
356     return false;
357   }
358
359   // Only delete cwd or outside cwd if --force enabled. Be careful, people!
360   if (!options.force) {
361     if (file.isPathCwd(filepath)) {
362       grunt.verbose.error();
363       grunt.fail.warn('Cannot delete the current working directory.');
364       return false;
365     } else if (!file.isPathInCwd(filepath)) {
366       grunt.verbose.error();
367       grunt.fail.warn('Cannot delete files outside the current working directory.');
368       return false;
369     }
370   }
371
372   try {
373     // Actually delete. Or not.
374     if (!nowrite) {
375       rimraf.sync(filepath);
376     }
377     grunt.verbose.ok();
378     return true;
379   } catch (e) {
380     grunt.verbose.error();
381     throw grunt.util.error('Unable to delete "' + filepath + '" file (' + e.message + ').', e);
382   }
383 };
384
385 // True if the file path exists.
386 file.exists = function() {
387   var filepath = path.join.apply(path, arguments);
388   return fs.existsSync(filepath);
389 };
390
391 // True if the file is a symbolic link.
392 file.isLink = function() {
393   var filepath = path.join.apply(path, arguments);
394   return file.exists(filepath) && fs.lstatSync(filepath).isSymbolicLink();
395 };
396
397 // True if the path is a directory.
398 file.isDir = function() {
399   var filepath = path.join.apply(path, arguments);
400   return file.exists(filepath) && fs.statSync(filepath).isDirectory();
401 };
402
403 // True if the path is a file.
404 file.isFile = function() {
405   var filepath = path.join.apply(path, arguments);
406   return file.exists(filepath) && fs.statSync(filepath).isFile();
407 };
408
409 // Is a given file path absolute?
410 file.isPathAbsolute = function() {
411   var filepath = path.join.apply(path, arguments);
412   return pathIsAbsolute(filepath);
413 };
414
415 // Do all the specified paths refer to the same path?
416 file.arePathsEquivalent = function(first) {
417   first = path.resolve(first);
418   for (var i = 1; i < arguments.length; i++) {
419     if (first !== path.resolve(arguments[i])) { return false; }
420   }
421   return true;
422 };
423
424 // Are descendant path(s) contained within ancestor path? Note: does not test
425 // if paths actually exist.
426 file.doesPathContain = function(ancestor) {
427   ancestor = path.resolve(ancestor);
428   var relative;
429   for (var i = 1; i < arguments.length; i++) {
430     relative = path.relative(path.resolve(arguments[i]), ancestor);
431     if (relative === '' || /\w+/.test(relative)) { return false; }
432   }
433   return true;
434 };
435
436 // Test to see if a filepath is the CWD.
437 file.isPathCwd = function() {
438   var filepath = path.join.apply(path, arguments);
439   try {
440     return file.arePathsEquivalent(fs.realpathSync(process.cwd()), fs.realpathSync(filepath));
441   } catch (e) {
442     return false;
443   }
444 };
445
446 // Test to see if a filepath is contained within the CWD.
447 file.isPathInCwd = function() {
448   var filepath = path.join.apply(path, arguments);
449   try {
450     return file.doesPathContain(fs.realpathSync(process.cwd()), fs.realpathSync(filepath));
451   } catch (e) {
452     return false;
453   }
454 };