/**1* Copyright 2013 Facebook, Inc.2*3* Licensed under the Apache License, Version 2.0 (the "License");4* you may not use this file except in compliance with the License.5* You may obtain a copy of the License at6*7* http://www.apache.org/licenses/LICENSE-2.08*9* Unless required by applicable law or agreed to in writing, software10* distributed under the License is distributed on an "AS IS" BASIS,11* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.12* See the License for the specific language governing permissions and13* limitations under the License.14*/1516var fs = require('fs');17var path = require('path');1819/**20* Scans directories for files with given extensions (async)21* Will not follow symlinks. Uses node.js native function to traverse, can22* be slower, but more safe than findNative23*24* @param {Array.<String>} scanDirs Directories to scan, ex: ['html/']25* @param {Array.<String>} extensions Extensions to searc for, ex: ['.js']26* @param {function|null} ignore Optional function to filter out paths27* @param {Function} callback28*/29function find(scanDirs, extensions, ignore, callback) {30var result = [];31var activeCalls = 0;3233function readdirRecursive(curDir) {34activeCalls++;35fs.readdir(curDir, function(err, names) {36activeCalls--;3738for (var i = 0; i < names.length; i++) {39names[i] = path.join(curDir, names[i]);40}4142names.forEach(function(curFile) {43if (ignore && ignore(curFile)) {44return;45}46activeCalls++;4748fs.lstat(curFile, function(err, stat) {49activeCalls--;5051if (!err && stat && !stat.isSymbolicLink()) {52if (stat.isDirectory()) {53readdirRecursive(curFile);54} else {55var ext = path.extname(curFile);56if (extensions.indexOf(ext) !== -1) {57result.push([curFile, stat.mtime.getTime()]);58}59}60}61if (activeCalls === 0) {62callback(result);63}64});65});6667if (activeCalls === 0) {68callback(result);69}70});71}7273scanDirs.forEach(readdirRecursive);74}7576/**77* Scans directories for files with given extensions (async)78* Will not follow symlinks. Uses native find shell script. Usually faster than79* node.js based implementation though as any shell command is suspectable to80* attacks. Use with caution.81*82* @param {Array.<String>} scanDirs Directories to scan, ex: ['html/']83* @param {Array.<String>} extensions Extensions to searc for, ex: ['.js']84* @param {function|null} ignore Optional function to filter out paths85* @param {Function} callback86*/87function findNative(scanDirs, extensions, ignore, callback) {88var os = require('os');89if(os.platform() == 'win32'){90return find(scanDirs,extensions,ignore,callback);91}92var spawn = require('child_process').spawn;93var args = [].concat(scanDirs);94args.push('-type', 'f');95extensions.forEach(function(ext, index) {96if (index) {97args.push('-o');98}99args.push('-iname');100args.push('*' + ext);101});102103var findProcess = spawn('find', args);104var stdout = '';105findProcess.stdout.setEncoding('utf-8');106findProcess.stdout.on('data', function(data) {107stdout += data;108});109110findProcess.stdout.on('close', function(code) {111// Split by lines, trimming the trailing newline112var lines = stdout.trim().split('\n');113if (ignore) {114var include = function(x) {115return !ignore(x);116};117lines = lines.filter(include);118}119var result = [];120var count = lines.length;121// for (var i = 0; i < count; i++){122// if (lines[i]) {123// var stat = fs.statSync(lines[i]);124// if (stat) {125// result.push([lines[i], stat.mtime.getTime()]);126// }127// }128// }129// callback(result);130lines.forEach(function(path) {131fs.stat(path, function(err, stat) {132if (stat && !stat.isDirectory()) {133result.push([path, stat.mtime.getTime()]);134}135if (--count === 0) {136callback(result);137}138});139});140});141}142143/**144* Wrapper for options for a find call145* @class146* @param {Object} options147*/148function FileFinder(options) {149this.scanDirs = options && options.scanDirs || ['.'];150this.extensions = options && options.extensions || ['.js'];151this.ignore = options && options.ignore || null;152this.useNative = options && options.useNative || false;153}154155/**156* @param {Function} callback157*/158FileFinder.prototype.find = function(callback) {159var impl = this.useNative ? findNative : find;160impl(this.scanDirs, this.extensions, this.ignore, callback);161};162163164module.exports = FileFinder;165module.exports.find = find;166module.exports.findNative = findNative;167168169