buenos-stats
Version:
Npm Package stats overview from your command line
305 lines (206 loc) • 7.1 kB
JavaScript
;
var $q = require('q');
var $cheerio = require('cheerio');
var $table = require('cli-table');
var $yargs = require('yargs');
var $https = require('https');
var $fs = require('fs');
// only support cli for now
cli();
function cli () {
// extract cli params
var args = $yargs.argv;
if (args['?']) {
showManual();
}
// remove current config?
if (args.unstore) {
try {
$fs.unlinkSync(__dirname + '/stored-query.json');
console.log('Removed stored config.');
process.exit();
} catch (e) {}
}
var config = {
users: [],
packages: []
};
if (!args.user && args._.length === 0) {
try {
var storedConfig = $fs.readFileSync(__dirname + '/stored-query.json');
storedConfig = JSON.parse(storedConfig);
config.users = storedConfig.users || config.users;
config.packages = storedConfig.packages || config.packages;
console.log('Loaded stored config');
} catch (e) {
showManual();
}
}
else {
if (args.user) {
config.users = Array.isArray(args.user) ? args.user : args.user.split(',');
}
// packages
if (args._.length) {
config.packages = args._;
}
// confirm command makes sense
if (config.packages.length === 0 && config.users.length === 0) {
showManual();
}
// store config?
if (args.store) {
$fs.writeFile(__dirname + '/stored-query.json', JSON.stringify(config, null, 4), function (err) {
if (err) { throw err }
else {
console.log('Stored query.');
}
});
}
}
buenosStats(config);
}
function showManual () {
console.log($fs.readFileSync(__dirname + '/help.txt').toString());
console.log();
try {
var storedConfig = $fs.readFileSync(__dirname + '/stored-query.json');
console.log('Stored config:');
console.log(storedConfig.toString());
} catch (e) {
console.log('No stored config found.');
}
process.exit();
}
function buenosStats (config) {
if (config.users) {
// get packages for each defined user
getUserPackages(config.users)
.then(function (packages) {
// merge with predefined packages
Array.prototype.push.apply(config.packages, packages);
// filter doubles, if any
config.packages = config.packages.filter(_arrayUniqueFilter);
})
.then(function () {
delete config.users;
return buenosStats(config);
});
}
else {
scrapePackages(config.packages)
.then(function (stats) {
// parse stats to array of arrays
var statsArray = [
['Package name', 'daily', 'weekly', 'monthly', 'dependents']
];
Object.keys(stats).forEach(function (packageName) {
var pkg = stats[packageName];
statsArray.push([packageName, pkg.dailyDownloads, pkg.weeklyDownloads, pkg.monthlyDownloads, pkg.dependents]);
});
// extract required column lengths
var colWidths = [0, 0, 0, 0, 0];
statsArray.forEach(function (result) {
var i = 0;
result.forEach(function (colValue) {
colWidths[i] = Math.max(colWidths[i], colValue.toString().length + 5);
i++;
});
});
var table = new $table({
head: statsArray.shift(),
colAligns: ['left', 'right', 'right', 'right', 'right'],
colWidths: colWidths
});
Array.prototype.push.apply(table, statsArray);
console.log(table.toString());
})
.catch(function (err) {
throw err;
});
}
}
function getUserPackages (users) {
var deferred = $q.defer();
var packages = [];
var deferreds = [];
users.forEach(function (user) {
deferreds.push(getPage('/~' + user)
.then(scrapeUserPage)
.then(function (userPackages) {
Array.prototype.push.apply(packages, userPackages);
}));
});
$q.allSettled(deferreds)
.then(function () {
deferred.resolve(packages);
});
return deferred.promise;
}
function scrapePackages (packages) {
var deferred = $q.defer();
var results = {};
var deferreds = [];
packages.forEach(function (packageName) {
results[packageName] = {};
deferreds.push(getPage('/package/' + packageName)
.then(scrapePackagePage)
.then(function (stats) {
results[packageName] = stats;
}));
});
$q.allSettled(deferreds)
.then(function () {
deferred.resolve(results);
});
return deferred.promise;
}
function getPage (path) {
var deferred = $q.defer();
var req = $https.request({
hostname: 'www.npmjs.com',
path: path,
method: 'GET'
}, function (res) {
var chunks = [];
res.on('data', function (chunk) {
chunks.push(chunk);
});
res.on('error', function (err) {
deferred.reject(err);
});
res.on('end', function () {
deferred.resolve(Buffer.concat(chunks).toString());
});
});
// send
req.end();
return deferred.promise;
}
function scrapePackagePage (html) {
var stats = {
dailyDownloads: '0',
weeklyDownloads: '0',
monthlyDownloads: '0',
dependents: '0'
};
// cheerio work here
var $ = $cheerio.load(html);
stats.dailyDownloads = $('.daily-downloads').text() || stats.dailyDownloads;
stats.weeklyDownloads = $('.weekly-downloads').text() || stats.weeklyDownloads;
stats.monthlyDownloads = $('.monthly-downloads').text() || stats.monthlyDownloads;
stats.dependents = $('.list-of-links.dependents a').length || stats.dependents;
return stats;
}
function scrapeUserPage (html) {
var packages = [];
// cheerio work here
var $ = $cheerio.load(html);
$('.collaborated-packages a').each(function (el) {
packages.push($(this).text());
});
return packages;
}
function _arrayUniqueFilter (value, index, self) {
return self.indexOf(value) === index;
}