File: /var/www/zaklada/html/node_modules/cover/bin/cover
#!/usr/bin/env node
// Copyright 2011 Itay Neeman
//
// Licensed under the MIT License
// TODO:
(function() {
var fs = require('fs');
var path = require('path');
var vm = require("vm");
var crypto = require('crypto');
var Module = require('module').Module;
var _ = require('underscore');
var _s = require('underscore.string');
var which = require('which').sync;
var commander = require('../contrib/commander');
var Table = require('../contrib/cli-table');
var pkg = require('../package.json');
var exists = fs.existsSync || path.existsSync;
var patch_console = require('../utils/console');
var cover = require("../index.js");
// Store the initial CWD, to harden us against process.chdir
var CWD = path.resolve(process.cwd());
// Get a list of the built-in reporters
var reporterOptions = "[" + _.keys(cover.reporters).join(",") + "]";
/* ====== Utilities ====== */
// Delete a directory recursively
var rmdirRecursiveSync = function(dirPath) {
var files = fs.readdirSync(dirPath);
for(var i = 0; i < files.length; i++) {
var filePath = path.join(dirPath, files[i]);
var file = fs.statSync(filePath);
if (file.isDirectory()) {
rmdirRecursiveSync(filePath);
}
else {
fs.unlinkSync(filePath);
}
}
fs.rmdirSync(dirPath);
};
/* ====== Config/Ignore File Handling ====== */
// Default config
var defaultConfigPath = path.join(path.resolve(__dirname), ".coverrc");
var defaultConfig = {};
var removeJSONComments = function(str) {
str = str || '';
str = str.replace(/\/\*[\s\S]*(?:\*\/)/g, ''); //everything between "/* */"
str = str.replace(/\/\/[^\n\r]*/g, ''); //everything after "//"
return str;
};
var readIgnoreFile = function(ignorePath, ignoreModules) {
var ignore = {};
// Get the full path relative to the CWD
var fullIgnorePath = path.resolve(CWD, ignorePath);
if (exists(fullIgnorePath)) {
// If we found an ignore file, read it in
var ignoreContents = fs.readFileSync(fullIgnorePath, "utf-8");
try {
// Note that directory fo the ignore path
var dir = path.dirname(fullIgnorePath);
var ignores = ignoreContents.split("\n");
// For each line (ignoring blank ones), get the full path,
// and add it to our ignore list.
for(var i = 0; i < ignores.length; i++) {
if (ignores[i].trim() === "") {
continue;
}
var filePath = path.resolve(dir, ignores[i].trim());
ignore[path.resolve(dir, filePath)] = true;
}
}
catch (ex) {
throw new Error("There was a problem parsing your .coverignore file at: " + ignorePath);
}
}
return ignore;
};
var readConfigFile = function(configFile) {
var config = {}
// Get the full path relative to the CWD
configFile = path.resolve(CWD, configFile);
if (!exists(configFile)) {
configFile = defaultConfigPath;
}
// Remove any comments from the JSON
var configContents = removeJSONComments(fs.readFileSync(path.resolve(configFile), "utf-8"));
try {
// If there is no content, we'll just add an empty object
if (configContents.trim() === "") {
configContents = "{}";
}
// Parse it and copy in the defaults
var rawConfig = JSON.parse(configContents);
for(var key in defaultConfig) {
config[key] = defaultConfig[key];
}
// Override the defaults with specific ones.
for(var key in rawConfig) {
config[key] = rawConfig[key];
}
}
catch (ex) {
throw new Error("There was a problem parsing your .coverrc file at: " + configFile);
}
return config;
};
var getOptionsAndIgnore = function(cmdline, options) {
options = options || {};
// Get configuration
var config = {};
if (cmdline.config || ".coverrc") {
if (_.isObject(cmdline.config)) {
config = cmdline.config;
}
else {
config = readConfigFile(cmdline.config || ".coverrc");
}
}
// Get ignore
var ignore = {};
if (cmdline.ignore || config.ignore || ".coverignore") {
if (_.isObject(cmdline.ignore)) {
ignore = cmdline.ignore;
}
else {
ignore = readIgnoreFile(cmdline.ignore || config.ignore || ".coverignore");
}
}
// If we didn't override it on the command line and the
// coverrc file says to ingore node_modules, then ignore
// it
if (options.modules !== true && config.modules === false) {
ignore[path.resolve(CWD, "node_modules")] = true;
}
return {
config: config,
ignore: ignore
}
};
defaultConfig = readConfigFile(defaultConfigPath);
/* ====== Context and Execution Handling ====== */
// Run a file with coverage enabled
var runFile = function(file, options, ignore, debug) {
if (!exists(file)) {
try {
file = which(file);
}
catch(ex) {
console.log("Could not find file: '" + file + "' -- exiting...");
return null;
}
}
else {
file = path.resolve(file);
}
var coverage = cover.cover(null, ignore, debug);
process.nextTick(function() {
try {
// Load up the new argv
options = options || [];
process.argv = ["node", file].concat(options)
// Load the file as the main module
Module.runMain(file, null, true)
}
catch(ex) {
console.log(ex.stack);
}
});
return coverage;
};
/* ====== Save/Load Coverage Data ====== */
var saveCoverageData = function(coverageData, configs, noPrecombine) {
if (!noPrecombine) {
savePrecombinedCoverageData(coverageData, configs);
}
// Setup the information we're going to save
var files = {};
var toSave = {
version: pkg.version,
files: files
};
_.each(coverageData, function(fileData, fileName, lst) {
// For each file, we hash the source to get a "version ID"
// for it
var stats = fileData.stats();
var fileSource = stats.source;
var md5 = crypto.createHash('md5');
md5.update(fileSource);
var hash = md5.digest('hex');
// We also save the stats and the hash,
// which is everything we need in order
// to be able to generate reports
files[fileName] = {
stats: stats,
hash: hash
}
});
// Turn it into JSON and write it out
var data = JSON.stringify(toSave);
// Get the ID for this data (md5 hash)
var dataMd5 = crypto.createHash('md5');
dataMd5.update(data);
var dataHash = dataMd5.digest('hex');
// Make the directory
var dataDirectory = path.join(CWD, configs.dataDirectory);
if (!exists(dataDirectory)) {
fs.mkdirSync(dataDirectory, "0755");
}
// Write out the file
var dataFilename = path.join(dataDirectory, configs.prefix + dataHash);
fs.writeFileSync(dataFilename, data);
};
var savePrecombinedCoverageData = function(coverageData, configs) {
// Setup the information we're going to save
var files = {};
var toSave = {
version: "0.2.0",
files: files
};
var coverageStore = require('../coverage_store');
_.each(coverageData, function(fileData, fileName, lst) {
var store = coverageStore.getStore(fileName);
// For each file, we hash the source to get a "version ID"
// for it
var fileSource = fileData.source;
var md5 = crypto.createHash('md5');
md5.update(fileSource);
var hash = md5.digest('hex');
// We also save the stats and the hash,
// which is everything we need in order
// to be able to generate reports
files[fileName] = {
nodes: store.nodes,
blocks: store.blocks,
hash: hash,
instrumentor: fileData.instrumentor.objectify()
}
});
// Turn it into JSON and write it out
var data = JSON.stringify(toSave);
// Get the ID for this data (md5 hash)
var dataMd5 = crypto.createHash('md5');
dataMd5.update(data);
var dataHash = dataMd5.digest('hex');
// Make the directory
var dataDirectory = path.join(CWD, configs.dataDirectory);
if (!exists(dataDirectory)) {
fs.mkdirSync(dataDirectory, "0755");
}
// Write out the file
var dataFilename = path.join(dataDirectory, "precombined_" + configs.prefix + dataHash);
fs.writeFileSync(dataFilename, data);
};
var loadCoverageData = function(filename, configs) {
var dataDirectory = configs.dataDirectory;
var dataPrefix = configs.prefix;
if (!filename) {
// If we weren't given a file, try and read it from the data directory.
var filesInDataDirectory = fs.readdirSync(dataDirectory);
var matchingFiles = _.filter(filesInDataDirectory, function(file) { return _s.startsWith(file, dataPrefix); });
if (matchingFiles.length > 1) {
// If there is more than one file, we'll use the last modified one.
var latestTime = 0;
for(var i = 0; i < matchingFiles.length; i++) {
var fileLatest = fs.statSync(path.join(dataDirectory, matchingFiles[i])).mtime;
if (fileLatest > latestTime) {
latestTime = fileLatest;
filename = matchingFiles[i];
}
}
}
else if (matchingFiles.length === 1) {
// If there was exactly one file, we'll use that one
filename = matchingFiles[0];
}
else if (matchingFiles.length === 0) {
// If there was no file, error.
console.error("Could not find a coverage data file. Please specify one.");
process.exit(1);
}
// Note the full path to the file
filename = path.join(dataDirectory, filename);
}
// If it doesn't exist, then error out
if (!exists(filename)) {
console.error("Coverage data file does not exist: " + filename);
process.exit(1);
}
// Read the file
var data = fs.readFileSync(filename, "utf-8");
var coverageData = JSON.parse(data);
var overall = {
filename: 'Overall',
missing : 0,
seen : 0,
total : 0,
blocks : {
total : 0,
seen : 0,
missing : 0
}
};
// Remake the objects into what the reporters expect
_.each(coverageData.files, function(fileData, fileName) {
var stats = fileData.stats;
fileData.filename = fileName;
fileData.stats = function() { return stats; };
fileData.source = stats.source;
_.each(['missing', 'seen', 'total'], function(field) {
overall[field] += stats[field];
overall.blocks[field] += stats.blocks[field];
});
});
overall.percentage = overall.seen / overall.total;
overall.blocks.percentage = overall.blocks.seen / overall.blocks.total;
coverageData.overall = {stats: function() { return overall; }};
return coverageData;
};
var loadPrecombinedCoverageData = function(configs) {
var dataDirectory = configs.dataDirectory;
var dataPrefix = configs.prefix;
var filesInDataDirectory = fs.readdirSync(dataDirectory);
var files = _.filter(filesInDataDirectory, function(file) {
return _s.startsWith(file, "precombined_" + dataPrefix);
}).map(function(file) {
return path.join(dataDirectory, file);
});
var datas = [];
_.each(files, function(filename) {
// If it doesn't exist, then error out
if (!exists(filename)) {
console.error("Coverage data file does not exist: " + filename);
process.exit(1);
}
// Read the file
var data = fs.readFileSync(filename, "utf-8");
datas.push(JSON.parse(data));
fs.unlinkSync(filename);
});
var coverageData = cover.load(datas);
return coverageData;
};
/* ====== Reporters ====== */
var reporterHandlers = {
html: function(coverageData, config) {
fileData = coverageData.files;
var files = _.sortBy(_.keys(fileData), function(file) { return file; });
var allStats = [];
var statsByFile = {};
var htmlDirectory = path.join(CWD, config.html.directory);
var templateDir = path.resolve(__dirname, "..", "templates");
var resourceDir = path.resolve(__dirname, "..", "resources");
function statsRow(name, stats) {
return [
name,
Math.floor(stats.percentage * 100) + "%",
stats.missing,
stats.total,
Math.floor(stats.blocks.percentage * 100) + "%",
stats.blocks.missing,
stats.blocks.total
];
}
var htmls = {};
for(var i = 0; i < files.length; i++) {
var filename = files[i];
htmls[filename] = cover.reporters.html.format(fileData[filename]);
}
// For each file, we compile a list of its summmary stats
for(var i = 0; i < files.length; i++) {
var filename = files[i];
var stats = fileData[filename].stats();
statsByFile[filename] = {
percentage: Math.floor(stats.percentage * 100) + "%",
missingLines: stats.missing,
seenLines: stats.seen,
partialLines: _.values(stats.coverage).filter(function(lineinfo) { return lineinfo.partial; }).length,
totalLines: stats.total,
blockPercentage: Math.floor(stats.blocks.percentage * 100) + "%",
missingBlocks: stats.blocks.missing,
seenBlocks: stats.blocks.seen,
totalBlocks: stats.blocks.total,
};
allStats.push(statsRow(path.relative(CWD, filename), stats));
}
allStats.push(statsRow('Overall', coverageData.overall.stats()));
// Delete the HTML directory if necessary, and then create it
if (exists(htmlDirectory)) {
rmdirRecursiveSync(htmlDirectory);
}
fs.mkdirSync(htmlDirectory, "755");
// For each HTML file, use the template to generate an HTML file,
// and write it out to disk
_.each(htmls, function(html, filename) {
var outputPath = path.join(htmlDirectory, filename.replace(/[\/|\:|\\]/g, "_") + ".html");
var htmlTemplateString = fs.readFileSync(path.join(templateDir, "file.html")).toString("utf-8");
var htmlTemplate = _.template(htmlTemplateString);
var completeHtml = htmlTemplate({
filename: filename,
code: html,
stats: statsByFile[filename]
});
fs.writeFileSync(outputPath, completeHtml);
});
// Compile the index file with the template, and write it out to disk
var indexTemplateString = fs.readFileSync(path.join(templateDir, "index.html")).toString("utf-8");
var indexTemplate = _.template(indexTemplateString);
var headers = ["Filename", "% Covered", "Missed Lines", "# Lines", "% Blocks", "Missed Blocks", "# Blocks"];
var fileUrls = {};
_.each(files, function(file) {
fileUrls[path.relative(CWD, file)] = file.replace(/[\/|\:|\\]/g, "_") + ".html";
});
var indexHtml = indexTemplate({ headers: headers, data: allStats, fileUrls: fileUrls });
fs.writeFileSync(path.join(htmlDirectory, "index.html"), indexHtml);
// Copy over the resource files
fs.link(path.join(resourceDir, "bootstrap.css"), path.join(htmlDirectory, "bootstrap.css"));
fs.link(path.join(resourceDir, "prettify.css"), path.join(htmlDirectory, "prettify.css"));
fs.link(path.join(resourceDir, "prettify.js"), path.join(htmlDirectory, "prettify.js"));
fs.link(path.join(resourceDir, "jquery.min.js"), path.join(htmlDirectory, "jquery.min.js"));
fs.link(path.join(resourceDir, "jquery.tablesorter.min.js"), path.join(htmlDirectory, "jquery.tablesorter.min.js"));
},
cli: function(coverageData, config) {
fileData = coverageData.files;
var files = _.sortBy(_.keys(fileData), function(file) { return file; });
var table = new Table({
head: ["Filename", "% Covered", "Missed Lines", "# Lines", "% Blocks", "Missed Blocks", "# Blocks"],
style : {
'padding-left' : 1,
'padding-right ': 1,
head: ['cyan']
},
colWidths: [cover.reporters.cli.MAX_FILENAME_LENGTH + 5, 10, 14, 10, 10, 14, 10]
});
for(var i = 0; i < files.length; i++) {
var filename = files[i];
table.push(cover.reporters.cli.format(fileData[filename]));
}
table.push(cover.reporters.cli.format(coverageData.overall));
console.log(table.toString());
},
plain: function(coverageData, config) {
fileData = coverageData.files;
var files = _.sortBy(_.keys(fileData), function(file) { return file; });
for(var i = 0; i < files.length; i++) {
var filename = files[i];
console.log(cover.reporters.plain.format(fileData[filename]));
}
},
json: function(coverageData, config) {
fileData = coverageData.files;
var files = _.sortBy(_.keys(fileData), function(file) { return file; });
var jsons = [];
for(var i = 0; i < files.length; i++) {
var filename = files[i];
jsons.push(cover.reporters.json.format(fileData[filename]));
}
jsons.push(cover.reporters.json.format(coverageData.overall));
console.log(JSON.stringify(jsons));
},
__catch_all: function(coverageData, config, reporter) {
}
};
/* ====== Commands ====== */
var run = function(file, cmdline, config, ignore, debug) {
if (debug) {
var debugDirectory = path.join(CWD, config.debugDirectory);
if (!exists(debugDirectory)) {
fs.mkdirSync(debugDirectory, "0755");
}
}
// Run the file
var coverage = runFile(file, cmdline, ignore, debug ? config.debugDirectory : null);
// Setup the on exit listener
process.on(
"exit",
function() {
// For debugging purposes, make sure we can print things
patch_console.patch();
if (!coverage) {
return;
}
coverage(function(coverageData) {
try {
saveCoverageData(coverageData, config);
}
catch(e) {
console.log(e.stack);
}
});
}
);
};
var report = function(reporterName, file, config, ignore) {
var coverageData = loadCoverageData(file, config);
reporterName = reporterName || "cli";
var reporter = "";
if (cover.reporters[reporterName]) {
reporter = cover.reporters[reporterName];
}
else {
try {
reporter = require(path.resolve(reporterName));
}
catch(ex) {
console.log("Could not find reporter: " + reporterName);
process.exit(1);
}
}
(reporterHandlers[reporterName] || reporterHandlers.__catch_all)(coverageData, config, reporter);
}
var combine = function(configFile, ignoreFile) {
var files = getOptionsAndIgnore({config: configFile, ignore: ignoreFile});
var config = files.config;
var ignore = files.ignore;
var coverageData = loadPrecombinedCoverageData(config);
saveCoverageData(coverageData, config, true);
}
var hook = function(configFile, ignoreFile) {
var files = getOptionsAndIgnore({config: configFile, ignore: ignoreFile});
var config = files.config;
var ignore = files.ignore;
var cover = require('../index.js');
coverage = cover.cover(null, ignore, global);
// Setup the on exit listener
process.on(
"exit",
function() {
// For debugging purposes, make sure we can print things
patch_console.patch();
coverage(function(coverageData) {
try {
saveCoverageData(coverageData, config);
}
catch(e) {
console.log(e.stack);
}
});
}
);
}
var hookAndReport = function(reporterName, configFile, ignoreFile) {
var files = getOptionsAndIgnore({config: configFile, ignore: ignoreFile});
var config = files.config;
var ignore = files.ignore;
hook(configFile, ignoreFile);
process.on("exit", function() {
report(reporterName, null, config, ignore);
});
}
var hookAndCombine = function(configFile, ignoreFile) {
var files = getOptionsAndIgnore({config: configFile, ignore: ignoreFile});
var config = files.config;
var ignore = files.ignore;
hook(configFile, ignoreFile);
process.on("exit", function() {
combine(config);
});
}
var hookAndCombineAndReport = function(reporterName, configFile, ignoreFile) {
var files = getOptionsAndIgnore({config: configFile, ignore: ignoreFile});
var config = files.config;
var ignore = files.ignore;
hook(configFile, ignoreFile);
process.on("exit", function() {
combine(config);
report(reporterName, null, config, ignore);
});
}
/* ====== Command Line Parsing ====== */
commander
.version(pkg.version)
.option("-c, --config <config>", "Path to a .coverrc file")
.option("-i, --ignore <ignore>", "Path to a .coverignore file")
commander.command("run <file>")
.description("Run a file and collect code coverage information for it")
.option("-d, --debug", "Enable debugging")
.option("-m, --modules", "Enable coverage for node_modules")
.action(function(file, options) {
var configs = getOptionsAndIgnore(this, options);
var config = configs.config;
var ignore = configs.ignore;
var debug = options.debug;
// Remove the command itself
var cmdline = this.args.slice(1);
run(file, cmdline, config, ignore, debug);
});
commander.command("report [reporter] [file]")
.description("Report on saved coverage data. Specify a coverage file and a reporter: " + reporterOptions)
.action(function(reporterName, file) {
var configs = getOptionsAndIgnore(this);
var config = configs.config;
var ignore = configs.ignore;
report(reporterName, file, config, ignore);
});
commander.command("combine")
.description("Combine multiple coverage files (from multiple runs) into a single, reportable file")
.action(function(reporterName, file) {
var configs = getOptionsAndIgnore(this);
var config = configs.config;
var ignore = configs.ignore;
combine(config, ignore);
});
var printHelp = function() {
console.log(commander.helpInformation());
}
commander.command("*")
.action(function() {
printHelp();
});
/* ====== Exports ====== */
module.exports = {
parse: function(argv) {
var cmdline = commander.parse(argv);
if (!cmdline.executedCommand) {
printHelp();
}
return cmdline;
},
run: run,
report: report,
hook: hook,
hookAndReport: hookAndReport,
combine: combine,
hookAndCombine: hookAndCombine,
hookAndCombineAndReport: hookAndCombineAndReport
};
})();
if (module === require.main) {
module.exports.parse(process.argv);
}