//[
// Copyright (c) 2011, Richard Miller-Smith & David Hammond.
// All rights reserved. Redistribution and use in source and binary forms,
// with or without modification, are permitted provided that the following
// conditions are met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following
// disclaimer in the documentation and/or other materials provided
// with the distribution.
// * Neither the name of the ignite.js project, nor the names of its
// contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//]
var ignite = require('ignite'),
path = require('path'),
_ = require('underscore.string');
function searchDirectory (fire, regExp, dirPath) {
var dirListing;
this.startState = 'ReadDir';
this.states = {
ReadDir: {
async: {
fn: 'fs.readdir',
fnArgs: [ dirPath ]
},
actions: {
'.done': 'DirListingStat',
'.err': function (err) {
fire.$factoryEvent('fsErr', err);
return '@exit';
}
},
exit: function (listing) {
dirListing = listing;
}
},
DirListingStat: {
guard: function () {
if (dirListing.length === 0) {
return '@exit';
}
},
async: {
fn: 'fs.lstat',
fnArgs: function () {
return [path.join(dirPath, dirListing.shift())];
}
},
actions: {
'.done': function (stat, fnArgs) {
if (stat.isFile()) {
return [ 'ReadFile', fnArgs[0] ];
} else if (stat.isDirectory()) {
fire.$factoryEvent('addDir', fnArgs[0]);
}
return '@self';
},
'.err': function (err) {
fire.$factoryEvent('fsErr', err);
return '@self';
}
}
},
ReadFile: {
guard: function (fileName) {
if (!fileName.match(/(?:\.js|\.c|\.cpp)$/)) {
return 'DirListingStat';
}
},
async: 'fs.readFile',
actions: {
'.done': function (data, fnArgs) {
return [ 'Match', data.toString('utf8'), fnArgs[0] ];
},
'.err': function (err) {
fire.$factoryEvent('fsErr', err);
return 'DirListingStat';
}
}
},
Match: {
work: {
fn: function (data, fileName) {
var i, lines, matches = [];
lines = data.split('\n');
for (i = 0; i < lines.length; i++) {
if (lines[i].match(this.regExp)) {
matches.push({
lineno: i + 1,
text: lines[i]
});
}
}
return ['done', fileName, matches];
},
ctx: { regExp: regExp }
},
actions: {
'.done': function (fileName, matches) {
if (matches.length !== 0) {
fire.$factoryEvent('addMatch', fileName, matches);
}
return 'DirListingStat';
}
}
}
};
};
searchDirectory.defaults = {
imports: { fs: require('fs') },
options: {
logLevel: 0
// If you want to offload the 'Match' state above to a separate process
// uncomment the following three lines (but, beware this makes it slower
// as the overhead outweighs the benefit).
// , plugins: {
// loadAs: { "work": "worker" }
// }
}
};
var processorFactory = new ignite.Factory(searchDirectory) ;
function srcGrep (fire, regExp, topDirPath,
numOfProcessors) {
var dirList = [ topDirPath ], numOfProcessors = numOfProcessors || 4,
results = {};
this.startState = 'ManageProcessors';
this.states = {
ManageProcessors: {
ticker: 5000,
entry: function () {
if (typeof regexp === 'string') {
regExp = new RegExp(regExp);
}
fire.$regEmitter('processor', processorFactory);
processorFactory.setThreshold.lessthan(numOfProcessors);
},
actions: {
'processor.threshold': function () {
if (dirList.length !== 0) {
processorFactory.spawn(regExp, dirList.shift());
}
},
'processor.addDir': function (dir) {
dirList.push(dir);
},
'processor.addMatch': function (fileName, matches) {
results[fileName] = matches;
},
'processor.fsErr': function (err) {
console.error(err);
},
'processor.quiet': function () {
return [ '@exit', results ];
},
'tick': function () {
console.log(processorFactory.addons.track());
console.log(JSON.stringify(process.memoryUsage()));
}
}
}
};
};
srcGrep.defaults = {
imports: searchDirectory.defaults.imports,
args: ['ignite', '.'],
callback: function (err, results) {
var fileName, matches, i, m;
if (!err) {
for (fileName in results) {
console.log('%s', fileName);
matches = results[fileName];
for (i in matches) {
m = matches[i];
console.log('%s: %s',
_.rpad(String(m.lineno), 4, ' ') , m.text);
}
}
}
}
};
module.exports = srcGrep;