lib/grunt/file.js

Maintainability

65.75

Lines of code

429

Created with Raphaël 2.1.002550751002013-2-252013-1-262012-12-272012-11-272012-10-282012-9-282012-8-292013-3-27Maintainability: 65.75

Created with Raphaël 2.1.001092183274362013-2-252013-1-262012-12-272012-11-272012-10-282012-9-282012-8-292013-3-27Lines of Code: 429

Difficulty

69.65

Estimated Errors

4.62

Function weight

By Complexity

Created with Raphaël 2.1.0file.delete9

By SLOC

Created with Raphaël 2.1.0file.expandMapping42
1
/*
2
 * grunt
3
 * http://gruntjs.com/
4
 *
5
 * Copyright (c) 2013 "Cowboy" Ben Alman
6
 * Licensed under the MIT license.
7
 * https://github.com/gruntjs/grunt/blob/master/LICENSE-MIT
8
 */
9
 
10
'use strict';
Column: 1 "Use the function form of "use strict"."
11
 
12
var grunt = require('../grunt');
Column: 13 "'require' is not defined."
13
 
14
// Nodejs libs.
15
var fs = require('fs');
Column: 10 "'require' is not defined."
16
var path = require('path');
Column: 12 "'require' is not defined."
17
 
18
// The module to be exported.
19
var file = module.exports = {};
Column: 12 "'module' is not defined."
20
 
21
// External libs.
22
file.glob = require('glob');
Column: 13 "'require' is not defined."
23
file.minimatch = require('minimatch');
Column: 18 "'require' is not defined."
24
file.findup = require('findup-sync');
Column: 15 "'require' is not defined."
25
var YAML = require('js-yaml');
Column: 12 "'require' is not defined."
26
var rimraf = require('rimraf');
Column: 14 "'require' is not defined."
27
var iconv = require('iconv-lite');
Column: 13 "'require' is not defined."
28
 
29
// Windows?
30
var win32 = process.platform === 'win32';
Column: 13 "'process' is not defined."
31
 
32
// Normalize \\ paths to / paths.
33
var unixifyPath = function(filepath) {
34
  if (win32) {
35
    return filepath.replace(/\\/g, '/');
36
  } else {
37
    return filepath;
38
  }
39
};
40
 
41
// Change the current base path (ie, CWD) to the specified path.
42
file.setBase = function() {
43
  var dirpath = path.join.apply(path, arguments);
44
  process.chdir(dirpath);
Column: 3 "'process' is not defined."
45
};
46
 
47
// Process specified wildcard glob patterns or filenames against a
48
// callback, excluding and uniquing files in the result set.
49
var processPatterns = function(patterns, fn) {
50
  // Filepaths to return.
51
  var result = [];
52
  // Iterate over flattened patterns array.
53
  grunt.util._.flatten(patterns).forEach(function(pattern) {
54
    // If the first character is ! it should be omitted
55
    var exclusion = pattern.indexOf('!') === 0;
56
    // If the pattern is an exclusion, remove the !
57
    if (exclusion) { pattern = pattern.slice(1); }
58
    // Find all matching files for this pattern.
59
    var matches = fn(pattern);
60
    if (exclusion) {
61
      // If an exclusion, remove matching files.
62
      result = grunt.util._.difference(result, matches);
63
    } else {
64
      // Otherwise add matching files.
65
      result = grunt.util._.union(result, matches);
66
    }
67
  });
68
  return result;
69
};
70
 
71
// Match a filepath or filepaths against one or more wildcard patterns. Returns
72
// all matching filepaths.
73
file.match = function(options, patterns, filepaths) {
74
  if (grunt.util.kindOf(options) !== 'object') {
75
    filepaths = patterns;
76
    patterns = options;
77
    options = {};
78
  }
79
  // Return empty set if either patterns or filepaths was omitted.
80
  if (patterns == null || filepaths == null) { return []; }
Column: 16 "Use '===' to compare with 'null'."
Column: 37 "Use '===' to compare with 'null'."
81
  // Normalize patterns and filepaths to arrays.
82
  if (!Array.isArray(patterns)) { patterns = [patterns]; }
83
  if (!Array.isArray(filepaths)) { filepaths = [filepaths]; }
84
  // Return empty set if there are no patterns or filepaths.
85
  if (patterns.length === 0 || filepaths.length === 0) { return []; }
86
  // Return all matching filepaths.
87
  return processPatterns(patterns, function(pattern) {
88
    return file.minimatch.match(filepaths, pattern, options);
89
  });
90
};
91
 
92
// Match a filepath or filepaths against one or more wildcard patterns. Returns
93
// true if any of the patterns match.
94
file.isMatch = function() {
95
  return file.match.apply(file, arguments).length > 0;
96
};
97
 
98
// Return an array of all file paths that match the given wildcard patterns.
99
file.expand = function() {
100
  var args = grunt.util.toArray(arguments);
101
  // If the first argument is an options object, save those options to pass
102
  // into the file.glob.sync method.
103
  var options = grunt.util.kindOf(args[0]) === 'object' ? args.shift() : {};
104
  // Use the first argument if it's an Array, otherwise convert the arguments
105
  // object to an array and use that.
106
  var patterns = Array.isArray(args[0]) ? args[0] : args;
107
  // Return empty set if there are no patterns or filepaths.
108
  if (patterns.length === 0) { return []; }
109
  // Return all matching filepaths.
110
  var matches = processPatterns(patterns, function(pattern) {
111
    // Find all matching files for this pattern.
112
    return file.glob.sync(pattern, options);
113
  });
114
  // Filter result set?
115
  if (options.filter) {
116
    matches = matches.filter(function(filepath) {
117
      filepath = path.join(options.cwd || '', filepath);
118
      try {
119
        if (typeof options.filter === 'function') {
120
          return options.filter(filepath);
121
        } else {
122
          // If the file is of the right type and exists, this should work.
123
          return fs.statSync(filepath)[options.filter]();
124
        }
125
      } catch(e) {
126
        // Otherwise, it's probably not the right type.
127
        return false;
128
      }
129
    });
130
  }
131
  return matches;
132
};
133
 
134
var pathSeparatorRe = /[\/\\]/g;
135
 
136
// Build a multi task "files" object dynamically.
137
file.expandMapping = function(patterns, destBase, options) {
138
  options = grunt.util._.defaults({}, options, {
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 (options.ext) {
154
      destPath = destPath.replace(/(\.[^\/]*)?$/, 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,
Column: 19 "Extra comma. (it breaks older versions of IE)"
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) {
Column: 12 "Use '===' to compare with 'null'."
185
    mode = parseInt('0777', 8) & (~process.umask());
Column: 36 "'process' is not defined."
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
 
217
// Read a file, return its contents.
218
file.read = function(filepath, options) {
219
  if (!options) { options = {}; }
220
  var contents;
221
  grunt.verbose.write('Reading ' + filepath + '...');
222
  try {
223
    contents = fs.readFileSync(String(filepath));
224
    // If encoding is not explicitly null, convert from encoded buffer to a
225
    // string. If no encoding was specified, use the default.
226
    if (options.encoding !== null) {
227
      contents = iconv.decode(contents, options.encoding || file.defaultEncoding);
228
      // Strip any BOM that might exist.
229
      if (contents.charCodeAt(0) === 0xFEFF) {
230
        contents = contents.substring(1);
231
      }
232
    }
233
    grunt.verbose.ok();
234
    return contents;
235
  } catch(e) {
236
    grunt.verbose.error();
237
    throw grunt.util.error('Unable to read "' + filepath + '" file (Error code: ' + e.code + ').', e);
238
  }
239
};
240
 
241
// Read a file, parse its contents, return an object.
242
file.readJSON = function(filepath, options) {
243
  var src = file.read(filepath, options);
244
  var result;
245
  grunt.verbose.write('Parsing ' + filepath + '...');
246
  try {
247
    result = JSON.parse(src);
248
    grunt.verbose.ok();
249
    return result;
250
  } catch(e) {
251
    grunt.verbose.error();
252
    throw grunt.util.error('Unable to parse "' + filepath + '" file (' + e.message + ').', e);
253
  }
254
};
255
 
256
// Read a YAML file, parse its contents, return an object.
257
file.readYAML = function(filepath, options) {
258
  var src = file.read(filepath, options);
259
  var result;
260
  grunt.verbose.write('Parsing ' + filepath + '...');
261
  try {
262
    result = YAML.load(src);
263
    grunt.verbose.ok();
264
    return result;
265
  } catch(e) {
266
    grunt.verbose.error();
267
    throw grunt.util.error('Unable to parse "' + filepath + '" file (' + e.problem + ').', e);
268
  }
269
};
270
 
271
// Write a file.
272
file.write = function(filepath, contents, options) {
273
  if (!options) { options = {}; }
274
  var nowrite = grunt.option('no-write');
275
  grunt.verbose.write((nowrite ? 'Not actually writing ' : 'Writing ') + filepath + '...');
276
  // Create path, if necessary.
277
  file.mkdir(path.dirname(filepath));
278
  try {
279
    // If contents is already a Buffer, don't try to encode it. If no encoding
280
    // was specified, use the default.
281
    if (!Buffer.isBuffer(contents)) {
Column: 10 "'Buffer' is not defined."
282
      contents = iconv.encode(contents, options.encoding || file.defaultEncoding);
283
    }
284
    // Actually write file.
285
    if (!nowrite) {
286
      fs.writeFileSync(filepath, contents);
287
    }
288
    grunt.verbose.ok();
289
    return true;
290
  } catch(e) {
291
    grunt.verbose.error();
292
    throw grunt.util.error('Unable to write "' + filepath + '" file (Error code: ' + e.code + ').', e);
293
  }
294
};
295
 
296
// Read a file, optionally processing its content, then write the output.
297
file.copy = function(srcpath, destpath, options) {
298
  if (!options) { options = {}; }
299
  // If a process function was specified, and noProcess isn't true or doesn't
300
  // match the srcpath, process the file's source.
301
  var process = options.process && options.noProcess !== true &&
302
    !(options.noProcess && file.isMatch(options.noProcess, srcpath));
303
  // If the file will be processed, use the encoding as-specified. Otherwise,
304
  // use an encoding of null to force the file to be read/written as a Buffer.
305
  var readWriteOptions = process ? options : {encoding: null};
306
  // Actually read the file.
307
  var contents = file.read(srcpath, readWriteOptions);
308
  if (process) {
309
    grunt.verbose.write('Processing source...');
310
    try {
311
      contents = options.process(contents, srcpath);
312
      grunt.verbose.ok();
313
    } catch(e) {
314
      grunt.verbose.error();
315
      throw grunt.util.error('Error while processing "' + srcpath + '" file.', e);
316
    }
317
  }
318
  // Abort copy if the process function returns false.
319
  if (contents === false) {
320
    grunt.verbose.writeln('Write aborted.');
321
  } else {
322
    file.write(destpath, contents, readWriteOptions);
323
  }
324
};
325
 
326
// Delete folders and files recursively
327
file.delete = function(filepath, options) {
Column: 6 "Expected an identifier and instead saw 'delete' (a reserved word)."
328
  filepath = String(filepath);
329
 
330
  var nowrite = grunt.option('no-write');
331
  if (!options) {
332
    options = {force: grunt.option('force') || false};
333
  }
334
 
335
  grunt.verbose.write((nowrite ? 'Not actually deleting ' : 'Deleting ') + filepath + '...');
336
 
337
  if (!file.exists(filepath)) {
338
    grunt.verbose.error();
339
    grunt.log.warn('Cannot delete nonexistent file.');
340
    return false;
341
  }
342
 
343
  // Only delete cwd or outside cwd if --force enabled. Be careful, people!
344
  if (!options.force) {
345
    if (file.isPathCwd(filepath)) {
346
      grunt.verbose.error();
347
      grunt.fail.warn('Cannot delete the current working directory.');
348
      return false;
349
    } else if (!file.isPathInCwd(filepath)) {
350
      grunt.verbose.error();
351
      grunt.fail.warn('Cannot delete files outside the current working directory.');
352
      return false;
353
    }
354
  }
355
 
356
  try {
357
    // Actually delete. Or not.
358
    if (!nowrite) {
359
      rimraf.sync(filepath);
360
    }
361
    grunt.verbose.ok();
362
    return true;
363
  } catch(e) {
364
    grunt.verbose.error();
365
    throw grunt.util.error('Unable to delete "' + filepath + '" file (' + e.message + ').', e);
366
  }
367
};
368
 
369
// True if the file path exists.
370
file.exists = function() {
371
  var filepath = path.join.apply(path, arguments);
372
  return fs.existsSync(filepath);
373
};
374
 
375
// True if the file is a symbolic link.
376
file.isLink = function() {
377
  var filepath = path.join.apply(path, arguments);
378
  return file.exists(filepath) && fs.lstatSync(filepath).isSymbolicLink();
379
};
380
 
381
// True if the path is a directory.
382
file.isDir = function() {
383
  var filepath = path.join.apply(path, arguments);
384
  return file.exists(filepath) && fs.statSync(filepath).isDirectory();
385
};
386
 
387
// True if the path is a file.
388
file.isFile = function() {
389
  var filepath = path.join.apply(path, arguments);
390
  return file.exists(filepath) && fs.statSync(filepath).isFile();
391
};
392
 
393
// Is a given file path absolute?
394
file.isPathAbsolute = function() {
395
  var filepath = path.join.apply(path, arguments);
396
  return path.resolve(filepath) === filepath.replace(/[\/\\]+$/, '');
397
};
398
 
399
// Do all the specified paths refer to the same path?
400
file.arePathsEquivalent = function(first) {
401
  first = path.resolve(first);
402
  for (var i = 1; i < arguments.length; i++) {
403
    if (first !== path.resolve(arguments[i])) { return false; }
404
  }
405
  return true;
406
};
407
 
408
// Are descendant path(s) contained within ancestor path? Note: does not test
409
// if paths actually exist.
410
file.doesPathContain = function(ancestor) {
411
  ancestor = path.resolve(ancestor);
412
  var relative;
413
  for (var i = 1; i < arguments.length; i++) {
414
    relative = path.relative(path.resolve(arguments[i]), ancestor);
415
    if (relative === '' || /\w+/.test(relative)) { return false; }
416
  }
417
  return true;
418
};
419
 
420
// Test to see if a filepath is the CWD.
421
file.isPathCwd = function() {
422
  var filepath = path.join.apply(path, arguments);
423
  try {
424
    return file.arePathsEquivalent(process.cwd(), fs.realpathSync(filepath));
Column: 36 "'process' is not defined."
425
  } catch(e) {
426
    return false;
427
  }
428
};
429
 
430
// Test to see if a filepath is contained within the CWD.
431
file.isPathInCwd = function() {
432
  var filepath = path.join.apply(path, arguments);
433
  try {
434
    return file.doesPathContain(process.cwd(), fs.realpathSync(filepath));
Column: 33 "'process' is not defined."
435
  } catch(e) {
436
    return false;
437
  }
438
};