5 * Copyright (c) 2012 "Cowboy" Ben Alman
6 * Licensed under the MIT license.
7 * https://github.com/gruntjs/grunt/blob/master/LICENSE-MIT
10 var grunt = require('../grunt');
13 var fs = require('fs');
14 var path = require('path');
15 // In Nodejs 0.8.0, existsSync moved from path -> fs.
16 var existsSync = fs.existsSync || path.existsSync;
18 // The module to be exported.
19 var file = module.exports = {};
22 file.glob = require('glob-whatev');
23 file.findup = require('../util/findup');
25 // Change the current base path (ie, CWD) to the specified path.
26 file.setBase = function() {
27 var dirpath = path.join.apply(path, arguments);
28 process.chdir(dirpath);
31 // Match a filepath against one or more wildcard patterns. Returns true if
32 // any of the patterns match.
33 file.isMatch = function(patterns, filepath) {
34 patterns = Array.isArray(patterns) ? patterns : [patterns];
35 return patterns.some(function(pattern) {
36 return file.glob.minimatch(filepath, pattern, {matchBase: true});
40 // Return an array of all file paths that match the given wildcard patterns.
41 file.expand = function() {
42 var args = grunt.utils.toArray(arguments);
43 // If the first argument is an options object, save those options to pass
44 // into the file.glob.glob method for minimatch to use.
45 var options = grunt.utils.kindOf(args[0]) === 'object' ? args.shift() : {};
46 // Use the first argument if it's an Array, otherwise convert the arguments
47 // object to an array and use that.
48 var patterns = Array.isArray(args[0]) ? args[0] : args;
49 // Generate a should-be-unique number.
50 var uid = +new Date();
51 // Return a flattened, uniqued array of matching file paths.
52 return grunt.utils._(patterns).chain().flatten().map(function(pattern) {
53 // If pattern is a template, process it accordingly.
54 pattern = grunt.template.process(pattern);
55 // Just return the pattern if it's an internal directive.
56 if (grunt.task.getDirectiveParts(pattern)) { return pattern; }
57 // Otherwise, expand paths.
58 return file.glob.glob(pattern, options);
59 }).flatten().uniq(false, function(filepath) {
60 // Only unique file paths, but don't unique <something> directives, in case
61 // they are repeated intentionally.
62 return grunt.task.getDirectiveParts(filepath) ? ++uid : filepath;
66 // Further filter file.expand.
67 function expandByType(type) {
68 var args = grunt.utils.toArray(arguments).slice(1);
69 return file.expand.apply(file, args).filter(function(filepath) {
70 // Just return the filepath if it's an internal directive.
71 if (grunt.task.getDirectiveParts(filepath)) { return filepath; }
73 return fs.statSync(filepath)[type]();
75 throw grunt.task.taskError(e.message, e);
80 // A few type-specific file expansion methods.
81 file.expandDirs = expandByType.bind(file, 'isDirectory');
82 file.expandFiles = expandByType.bind(file, 'isFile');
84 // Return an array of all file paths that match the given wildcard patterns,
85 // plus any URLs that were passed at the end.
86 file.expandFileURLs = function() {
87 // Use the first argument if it's an Array, otherwise convert the arguments
88 // object to an array and use that.
89 var patterns = Array.isArray(arguments[0]) ? arguments[0] : grunt.utils.toArray(arguments);
91 // Filter all URLs out of patterns list and store them in a separate array.
92 patterns = patterns.filter(function(pattern) {
93 if (/^(?:file|https?):\/\//i.test(pattern)) {
94 // Push onto urls array.
96 // Remove from patterns array.
99 // Otherwise, keep pattern.
102 // Return expanded filepaths with urls at end.
103 return file.expandFiles(patterns).map(function(filepath) {
104 var abspath = path.resolve(filepath);
105 // Convert C:\foo\bar style paths to /C:/foo/bar.
106 if (abspath.indexOf('/') !== 0) {
107 abspath = ('/' + abspath).replace(/\\/g, '/');
109 return 'file://' + abspath;
113 // Like mkdir -p. Create a directory and any intermediary directories.
114 file.mkdir = function(dirpath) {
115 if (grunt.option('no-write')) { return; }
116 dirpath.split(/[\/\\]/).reduce(function(parts, part) {
118 var subpath = path.resolve(parts);
119 if (!existsSync(subpath)) {
121 fs.mkdirSync(subpath, '0755');
123 throw grunt.task.taskError('Unable to create directory "' + subpath + '" (Error code: ' + e.code + ').', e);
130 // Recurse into a directory, executing callback for each file.
131 file.recurse = function recurse(rootdir, callback, subdir) {
132 var abspath = subdir ? path.join(rootdir, subdir) : rootdir;
133 fs.readdirSync(abspath).forEach(function(filename) {
134 var filepath = path.join(abspath, filename);
135 if (fs.statSync(filepath).isDirectory()) {
136 recurse(rootdir, callback, path.join(subdir, filename));
138 callback(path.join(rootdir, subdir, filename), rootdir, subdir, filename);
143 // Is a given file path absolute?
144 file.isPathAbsolute = function() {
145 var filepath = path.join.apply(path, arguments);
146 return path.resolve(filepath) === filepath;
150 file.write = function(filepath, contents) {
151 var nowrite = grunt.option('no-write');
152 grunt.verbose.write((nowrite ? 'Not actually writing ' : 'Writing ') + filepath + '...');
153 // Create path, if necessary.
154 file.mkdir(path.dirname(filepath));
157 // Actually write file.
158 fs.writeFileSync(filepath, contents);
163 grunt.verbose.error();
164 throw grunt.task.taskError('Unable to write "' + filepath + '" file (Error code: ' + e.code + ').', e);
168 // Read a file, return its contents.
169 file.read = function(filepath, encoding) {
171 grunt.verbose.write('Reading ' + filepath + '...');
173 src = fs.readFileSync(String(filepath), encoding ? null : 'utf8');
177 grunt.verbose.error();
178 throw grunt.task.taskError('Unable to read "' + filepath + '" file (Error code: ' + e.code + ').', e);
182 // Read a file, optionally processing its content, then write the output.
183 file.copy = function(srcpath, destpath, options) {
184 if (!options) { options = {}; }
185 var src = file.read(srcpath, true);
186 if (options.process && options.noProcess !== true &&
187 !(options.noProcess && file.isMatch(options.noProcess, srcpath))) {
188 grunt.verbose.write('Processing source...');
190 src = options.process(src.toString('utf8'));
193 grunt.verbose.error();
194 throw grunt.task.taskError('Error while processing "' + srcpath + '" file.', e);
197 // Abort copy if the process function returns false.
199 grunt.verbose.writeln('Write aborted.');
201 file.write(destpath, src);
205 // Read a file, parse its contents, return an object.
206 file.readJSON = function(filepath) {
207 var src = this.read(String(filepath));
209 grunt.verbose.write('Parsing ' + filepath + '...');
211 result = JSON.parse(src);
215 grunt.verbose.error();
216 throw grunt.task.taskError('Unable to parse "' + filepath + '" file (' + e.message + ').', e);
220 // Clear the require cache for all passed filepaths.
221 file.clearRequireCache = function() {
222 // If a non-string argument is passed, it's an array of filepaths, otherwise
223 // each filepath is passed individually.
224 var filepaths = typeof arguments[0] !== 'string' ? arguments[0] : grunt.utils.toArray(arguments);
225 // For each filepath, clear the require cache, if necessary.
226 filepaths.forEach(function(filepath) {
227 var abspath = path.resolve(filepath);
228 if (require.cache[abspath]) {
229 grunt.verbose.write('Clearing require cache for "' + filepath + '" file...').ok();
230 delete require.cache[abspath];
235 // Access files in the user's ".grunt" folder.
236 file.userDir = function() {
237 var dirpath = path.join.apply(path, arguments);
238 var win32 = process.platform === 'win32';
239 var homepath = process.env[win32 ? 'USERPROFILE' : 'HOME'];
240 dirpath = path.resolve(homepath, '.grunt', dirpath);
241 return existsSync(dirpath) ? dirpath : null;