2 var BINARY_EXTENSIONS, COMPRESSED_EXTENSIONS, IMAGE_EXTENSIONS, MARKDOWN_EXTENSIONS, Module, async, fs, fsPlus, isMoveTargetValid, isMoveTargetValidSync, isPathValid, lstatSyncNoException, mkdirp, path, rimraf, statSyncNoException, _,
7 Module = require('module');
9 path = require('path');
11 _ = require('underscore-plus');
13 async = require('async');
15 mkdirp = require('mkdirp');
17 rimraf = require('rimraf');
20 getHomeDirectory: function() {
21 if (process.platform === 'win32') {
22 return process.env.USERPROFILE;
24 return process.env.HOME;
27 absolute: function(relativePath) {
29 if (relativePath == null) {
32 homeDir = fsPlus.getHomeDirectory();
33 if (relativePath === '~') {
34 relativePath = homeDir;
35 } else if (relativePath.indexOf('~/') === 0) {
36 relativePath = "" + homeDir + (relativePath.substring(1));
39 return fs.realpathSync(relativePath);
45 normalize: function(pathToNormalize) {
46 var home, normalizedPath;
47 if (pathToNormalize == null) {
50 normalizedPath = path.normalize(pathToNormalize.toString());
51 if (home = fsPlus.getHomeDirectory()) {
52 if (normalizedPath === '~') {
53 normalizedPath = home;
54 } else if (normalizedPath.indexOf("~" + path.sep) === 0) {
55 normalizedPath = "" + home + (normalizedPath.substring(1));
58 return normalizedPath;
60 getAppDataDirectory: function() {
61 switch (process.platform) {
63 return fsPlus.absolute('~/Library/Application Support');
67 return process.env.APPDATA;
72 isAbsolute: function(pathToCheck) {
73 if (pathToCheck == null) {
76 if (process.platform === 'win32') {
77 if (pathToCheck[1] === ':') {
80 if (pathToCheck[0] === '\\' && pathToCheck[1] === '\\') {
84 return pathToCheck[0] === '/';
88 existsSync: function(pathToCheck) {
89 return isPathValid(pathToCheck) && (statSyncNoException(pathToCheck) !== false);
91 isDirectorySync: function(directoryPath) {
93 if (!isPathValid(directoryPath)) {
96 if (stat = statSyncNoException(directoryPath)) {
97 return stat.isDirectory();
102 isDirectory: function(directoryPath, done) {
103 if (!isPathValid(directoryPath)) {
106 return fs.stat(directoryPath, function(error, stat) {
110 return done(stat.isDirectory());
114 isFileSync: function(filePath) {
116 if (!isPathValid(filePath)) {
119 if (stat = statSyncNoException(filePath)) {
120 return stat.isFile();
125 isSymbolicLinkSync: function(symlinkPath) {
127 if (!isPathValid(symlinkPath)) {
130 if (stat = lstatSyncNoException(symlinkPath)) {
131 return stat.isSymbolicLink();
136 isSymbolicLink: function(symlinkPath, callback) {
137 if (isPathValid(symlinkPath)) {
138 return fs.lstat(symlinkPath, function(error, stat) {
139 return typeof callback === "function" ? callback((stat != null) && stat.isSymbolicLink()) : void 0;
142 return process.nextTick(function() {
143 return typeof callback === "function" ? callback(false) : void 0;
147 isExecutableSync: function(pathToCheck) {
149 if (!isPathValid(pathToCheck)) {
152 if (stat = statSyncNoException(pathToCheck)) {
153 return (stat.mode & 0x1ff & 1) !== 0;
158 getSizeSync: function(pathToCheck) {
160 if (isPathValid(pathToCheck)) {
161 return (_ref = statSyncNoException(pathToCheck).size) != null ? _ref : -1;
166 listSync: function(rootPath, extensions) {
168 if (!fsPlus.isDirectorySync(rootPath)) {
171 paths = fs.readdirSync(rootPath);
173 paths = fsPlus.filterExtensions(paths, extensions);
175 paths = paths.sort(function(a, b) {
176 return a.toLowerCase().localeCompare(b.toLowerCase());
178 paths = paths.map(function(childPath) {
179 return path.join(rootPath, childPath);
184 var done, extensions, rest, rootPath;
185 rootPath = arguments[0], rest = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
186 if (rest.length > 1) {
187 extensions = rest.shift();
190 return fs.readdir(rootPath, function(error, paths) {
195 paths = fsPlus.filterExtensions(paths, extensions);
197 paths = paths.sort(function(a, b) {
198 return a.toLowerCase().localeCompare(b.toLowerCase());
200 paths = paths.map(function(childPath) {
201 return path.join(rootPath, childPath);
203 return done(null, paths);
207 filterExtensions: function(paths, extensions) {
208 extensions = extensions.map(function(ext) {
212 return '.' + ext.replace(/^\./, '');
215 return paths.filter(function(pathToCheck) {
216 return _.include(extensions, path.extname(pathToCheck));
219 listTreeSync: function(rootPath) {
222 onPath = function(childPath) {
223 paths.push(childPath);
226 fsPlus.traverseTreeSync(rootPath, onPath, onPath);
229 move: function(source, target, callback) {
230 return isMoveTargetValid(source, target, function(isMoveTargetValidErr, isTargetValid) {
231 var error, targetParentPath;
232 if (isMoveTargetValidErr) {
233 callback(isMoveTargetValidErr);
236 if (!isTargetValid) {
237 error = new Error("'" + target + "' already exists.");
238 error.code = 'EEXIST';
242 targetParentPath = path.dirname(target);
243 return fs.exists(targetParentPath, function(targetParentExists) {
244 if (targetParentExists) {
245 fs.rename(source, target, callback);
248 return fsPlus.makeTree(targetParentPath, function(makeTreeErr) {
250 callback(makeTreeErr);
253 return fs.rename(source, target, callback);
258 moveSync: function(source, target) {
259 var error, targetParentPath;
260 if (!isMoveTargetValidSync(source, target)) {
261 error = new Error("'" + target + "' already exists.");
262 error.code = 'EEXIST';
265 targetParentPath = path.dirname(target);
266 if (!fs.existsSync(targetParentPath)) {
267 fsPlus.makeTreeSync(targetParentPath);
269 return fs.renameSync(source, target);
271 removeSync: function(pathToRemove) {
272 return rimraf.sync(pathToRemove);
274 remove: function(pathToRemove, callback) {
275 return rimraf(pathToRemove, callback);
277 writeFileSync: function(filePath, content, options) {
278 mkdirp.sync(path.dirname(filePath));
279 return fs.writeFileSync(filePath, content, options);
281 writeFile: function(filePath, content, options, callback) {
282 callback = _.last(arguments);
283 return mkdirp(path.dirname(filePath), function(error) {
285 return typeof callback === "function" ? callback(error) : void 0;
287 return fs.writeFile(filePath, content, options, callback);
291 copy: function(sourcePath, destinationPath, done) {
292 return mkdirp(path.dirname(destinationPath), function(error) {
293 var destinationStream, sourceStream;
295 if (typeof done === "function") {
300 sourceStream = fs.createReadStream(sourcePath);
301 sourceStream.on('error', function(error) {
302 if (typeof done === "function") {
307 destinationStream = fs.createWriteStream(destinationPath);
308 destinationStream.on('error', function(error) {
309 if (typeof done === "function") {
314 destinationStream.on('close', function() {
315 if (typeof done === "function") {
320 return sourceStream.pipe(destinationStream);
323 copySync: function(sourcePath, destinationPath) {
324 var content, destinationFilePath, source, sourceFilePath, sources, _i, _len, _results;
325 sources = fs.readdirSync(sourcePath);
326 mkdirp.sync(destinationPath);
328 for (_i = 0, _len = sources.length; _i < _len; _i++) {
329 source = sources[_i];
330 sourceFilePath = path.join(sourcePath, source);
331 destinationFilePath = path.join(destinationPath, source);
332 if (fsPlus.isDirectorySync(sourceFilePath)) {
333 _results.push(fsPlus.copySync(sourceFilePath, destinationFilePath));
335 content = fs.readFileSync(sourceFilePath);
336 _results.push(fs.writeFileSync(destinationFilePath, content));
341 makeTreeSync: function(directoryPath) {
342 if (!fsPlus.isDirectorySync(directoryPath)) {
343 return mkdirp.sync(directoryPath);
346 makeTree: function(directoryPath, callback) {
347 return fsPlus.isDirectory(directoryPath, function(exists) {
349 return typeof callback === "function" ? callback() : void 0;
351 return mkdirp(directoryPath, function(error) {
352 return typeof callback === "function" ? callback(error) : void 0;
356 traverseTreeSync: function(rootPath, onFile, onDirectory) {
358 if (onDirectory == null) {
359 onDirectory = onFile;
361 if (!fsPlus.isDirectorySync(rootPath)) {
364 traverse = function(directoryPath, onFile, onDirectory) {
365 var childPath, file, linkStats, stats, _i, _len, _ref;
366 _ref = fs.readdirSync(directoryPath);
367 for (_i = 0, _len = _ref.length; _i < _len; _i++) {
369 childPath = path.join(directoryPath, file);
370 stats = fs.lstatSync(childPath);
371 if (stats.isSymbolicLink()) {
372 if (linkStats = statSyncNoException(childPath)) {
376 if (stats.isDirectory()) {
377 if (onDirectory(childPath)) {
378 traverse(childPath, onFile, onDirectory);
380 } else if (stats.isFile()) {
386 return traverse(rootPath, onFile, onDirectory);
388 traverseTree: function(rootPath, onFile, onDirectory, onDone) {
389 return fs.readdir(rootPath, function(error, files) {
390 var file, queue, _i, _len, _results;
392 return typeof onDone === "function" ? onDone() : void 0;
394 queue = async.queue(function(childPath, callback) {
395 return fs.stat(childPath, function(error, stats) {
397 return callback(error);
398 } else if (stats.isFile()) {
401 } else if (stats.isDirectory()) {
402 if (onDirectory(childPath)) {
403 return fs.readdir(childPath, function(error, files) {
406 return callback(error);
408 for (_i = 0, _len = files.length; _i < _len; _i++) {
410 queue.unshift(path.join(childPath, file));
423 queue.concurrency = 1;
424 queue.drain = onDone;
426 for (_i = 0, _len = files.length; _i < _len; _i++) {
428 _results.push(queue.push(path.join(rootPath, file)));
434 md5ForPath: function(pathToDigest) {
436 contents = fs.readFileSync(pathToDigest);
437 return require('crypto').createHash('md5').update(contents).digest('hex');
439 resolve: function() {
440 var args, candidatePath, extensions, loadPath, loadPaths, pathToResolve, resolvedPath, _i, _len, _ref;
441 args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
442 if (_.isArray(_.last(args))) {
443 extensions = args.pop();
445 pathToResolve = (_ref = args.pop()) != null ? _ref.toString() : void 0;
447 if (!pathToResolve) {
450 if (fsPlus.isAbsolute(pathToResolve)) {
451 if (extensions && (resolvedPath = fsPlus.resolveExtension(pathToResolve, extensions))) {
454 if (fsPlus.existsSync(pathToResolve)) {
455 return pathToResolve;
459 for (_i = 0, _len = loadPaths.length; _i < _len; _i++) {
460 loadPath = loadPaths[_i];
461 candidatePath = path.join(loadPath, pathToResolve);
463 if (resolvedPath = fsPlus.resolveExtension(candidatePath, extensions)) {
467 if (fsPlus.existsSync(candidatePath)) {
468 return fsPlus.absolute(candidatePath);
474 resolveOnLoadPath: function() {
476 args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
477 loadPaths = Module.globalPaths.concat(module.paths);
478 return fsPlus.resolve.apply(fsPlus, __slice.call(loadPaths).concat(__slice.call(args)));
480 resolveExtension: function(pathToResolve, extensions) {
481 var extension, pathWithExtension, _i, _len;
482 for (_i = 0, _len = extensions.length; _i < _len; _i++) {
483 extension = extensions[_i];
484 if (extension === "") {
485 if (fsPlus.existsSync(pathToResolve)) {
486 return fsPlus.absolute(pathToResolve);
489 pathWithExtension = pathToResolve + "." + extension.replace(/^\./, "");
490 if (fsPlus.existsSync(pathWithExtension)) {
491 return fsPlus.absolute(pathWithExtension);
497 isCompressedExtension: function(ext) {
498 return COMPRESSED_EXTENSIONS.hasOwnProperty(ext);
500 isImageExtension: function(ext) {
501 return IMAGE_EXTENSIONS.hasOwnProperty(ext);
503 isPdfExtension: function(ext) {
504 return ext === '.pdf';
506 isBinaryExtension: function(ext) {
507 return BINARY_EXTENSIONS.hasOwnProperty(ext);
509 isReadmePath: function(readmePath) {
511 extension = path.extname(readmePath);
512 base = path.basename(readmePath, extension).toLowerCase();
513 return base === 'readme' && (extension === '' || fsPlus.isMarkdownExtension(extension));
515 isMarkdownExtension: function(ext) {
516 return MARKDOWN_EXTENSIONS.hasOwnProperty(ext);
518 isCaseInsensitive: function() {
519 var lowerCaseStat, upperCaseStat;
520 if (fsPlus.caseInsensitiveFs == null) {
521 lowerCaseStat = statSyncNoException(process.execPath.toLowerCase());
522 upperCaseStat = statSyncNoException(process.execPath.toUpperCase());
523 if (lowerCaseStat && upperCaseStat) {
524 fsPlus.caseInsensitiveFs = lowerCaseStat.dev === upperCaseStat.dev && lowerCaseStat.ino === upperCaseStat.ino;
526 fsPlus.caseInsensitiveFs = false;
529 return fsPlus.caseInsensitiveFs;
531 isCaseSensitive: function() {
532 return !fsPlus.isCaseInsensitive();
536 statSyncNoException = fs.statSyncNoException, lstatSyncNoException = fs.lstatSyncNoException;
538 if (statSyncNoException == null) {
539 statSyncNoException = function() {
541 args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
543 return fs.statSync.apply(fs, args);
551 if (lstatSyncNoException == null) {
552 lstatSyncNoException = function() {
554 args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
556 return fs.lstatSync.apply(fs, args);
564 BINARY_EXTENSIONS = {
575 COMPRESSED_EXTENSIONS = {
604 MARKDOWN_EXTENSIONS = {
614 isPathValid = function(pathToCheck) {
615 return (pathToCheck != null) && typeof pathToCheck === 'string' && pathToCheck.length > 0;
618 isMoveTargetValid = function(source, target, callback) {
619 return fs.stat(source, function(oldErr, oldStat) {
624 return fs.stat(target, function(newErr, newStat) {
625 if (newErr && newErr.code === 'ENOENT') {
626 callback(void 0, true);
629 return callback(void 0, source.toLowerCase() === target.toLowerCase() && oldStat.dev === newStat.dev && oldStat.ino === newStat.ino);
634 isMoveTargetValidSync = function(source, target) {
635 var newStat, oldStat;
636 oldStat = statSyncNoException(source);
637 newStat = statSyncNoException(target);
638 if (!(oldStat && newStat)) {
641 return source.toLowerCase() === target.toLowerCase() && oldStat.dev === newStat.dev && oldStat.ino === newStat.ino;
644 module.exports = _.extend({}, fs, fsPlus);