1 |
|
/* |
2 |
|
Copyright (c) 2012, Yahoo! Inc. All rights reserved. |
3 |
|
Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms. |
4 |
|
*/ |
5 |
|
|
6 |
1 |
var path = require('path'), |
7 |
|
mkdirp = require('mkdirp'), |
8 |
|
async = require('async'), |
9 |
|
fs = require('fs'), |
10 |
|
filesFor = require('../util/file-matcher').filesFor, |
11 |
|
nopt = require('nopt'), |
12 |
|
Instrumenter = require('../instrumenter'), |
13 |
|
inputError = require('../util/input-error'), |
14 |
|
formatOption = require('../util/help-formatter').formatOption, |
15 |
|
util = require('util'), |
16 |
|
Command = require('./index'), |
17 |
|
verbose; |
18 |
|
|
19 |
1 |
function processFiles(instrumenter, inputDir, outputDir, relativeNames) { |
20 |
|
var processor = function (name, callback) { |
21 |
|
var inputFile = path.resolve(inputDir, name), |
22 |
|
outputFile = path.resolve(outputDir, name), |
23 |
|
oDir = path.dirname(outputFile); |
24 |
|
|
25 |
|
mkdirp.sync(oDir); |
26 |
|
fs.readFile(inputFile, 'utf8', function (err, data) { |
27 |
|
if (err) { return callback(err, name); } |
28 |
|
instrumenter.instrument(data, inputFile, function (iErr, instrumented) { |
29 |
|
if (iErr) { return callback(iErr, name); } |
30 |
|
fs.writeFile(outputFile, instrumented, 'utf8', function (err) { |
31 |
|
return callback(err, name); |
32 |
|
}); |
33 |
|
}); |
34 |
|
}); |
35 |
|
}, |
36 |
|
q = async.queue(processor, 10), |
37 |
|
errors = [], |
38 |
|
count = 0, |
39 |
|
startTime = new Date().getTime(); |
40 |
|
|
41 |
|
q.push(relativeNames, function (err, name) { |
42 |
|
var inputFile, outputFile; |
43 |
|
if (err) { |
44 |
|
errors.push({ file: name, error: err.message || err.toString() }); |
45 |
|
inputFile = path.resolve(inputDir, name); |
46 |
|
outputFile = path.resolve(outputDir, name); |
47 |
|
fs.writeFileSync(outputFile, fs.readFileSync(inputFile)); |
48 |
|
} |
49 |
|
if (verbose) { |
50 |
|
console.log('Processed: ' + name); |
51 |
|
} else { |
52 |
|
if (count % 100 === 0) { process.stdout.write('.'); } |
53 |
|
} |
54 |
|
count += 1; |
55 |
|
}); |
56 |
|
|
57 |
|
q.drain = function () { |
58 |
|
var endTime = new Date().getTime(); |
59 |
|
console.log('\nProcessed [' + count + '] files in ' + Math.floor((endTime - startTime) / 1000) + ' secs'); |
60 |
|
if (errors.length > 0) { |
61 |
|
console.log('The following ' + errors.length + ' file(s) had errors and were copied as-is'); |
62 |
|
console.log(errors); |
63 |
|
} |
64 |
|
}; |
65 |
|
} |
66 |
|
|
67 |
|
|
68 |
1 |
function InstrumentCommand() { |
69 |
6 |
Command.call(this); |
70 |
|
} |
71 |
|
|
72 |
1 |
InstrumentCommand.TYPE = 'instrument'; |
73 |
1 |
util.inherits(InstrumentCommand, Command); |
74 |
|
|
75 |
1 |
Command.mix(InstrumentCommand, { |
76 |
|
synopsis: function synopsis() { |
77 |
5 |
return "instruments a file or a directory tree and writes the instrumented code to the desired output location"; |
78 |
|
}, |
79 |
|
|
80 |
|
usage: function () { |
81 |
2 |
console.error('\nUsage: ' + this.toolName() + ' ' + this.type() + ' <options> <file-or-directory>\n\nOptions are:\n\n' |
82 |
|
+ [ |
83 |
|
formatOption('--output <file-or-dir>', 'The output file or directory. This is required when the input is a directory, defaults to standard output when input is a file'), |
84 |
|
formatOption('-x <exclude-pattern> [-x <exclude-pattern>]', 'one or more fileset patterns (e.g. "**/vendor/**" to ignore all files under a vendor directory). Also see the --default-excludes option'), |
85 |
|
formatOption('--variable <global-coverage-variable-name>', 'change the variable name of the global coverage variable from the default value of `__coverage__` to something else'), |
86 |
|
formatOption('--embed-source', 'embed source code into the coverage object, defaults to false'), |
87 |
|
formatOption('--[no-]compact', 'produce [non]compact output, defaults to compact') |
88 |
|
].join('\n\n') + '\n'); |
89 |
2 |
console.error('\n'); |
90 |
|
}, |
91 |
|
|
92 |
|
run: function (args) { |
93 |
|
|
94 |
|
var config = { |
95 |
|
output: path, |
96 |
|
x: [Array, String], |
97 |
|
variable: String, |
98 |
|
compact: Boolean, |
99 |
|
verbose: Boolean, |
100 |
|
'embed-source': Boolean |
101 |
|
}, |
102 |
|
opts = nopt(config, { v : '--verbose' }, args, 0), |
103 |
|
cmdArgs = opts.argv.remain, |
104 |
|
file, |
105 |
|
stats, |
106 |
|
stream, |
107 |
|
instrumenter = new Instrumenter({ coverageVariable: opts.variable }); |
108 |
|
|
109 |
|
verbose = opts.verbose; |
110 |
|
if (cmdArgs.length !== 1) { |
111 |
|
throw inputError.create('Need exactly one filename/ dirname argument for the instrument command!'); |
112 |
|
} |
113 |
|
if (typeof opts.compact === 'undefined') { |
114 |
|
opts.compact = true; |
115 |
|
} |
116 |
|
|
117 |
|
instrumenter = new Instrumenter({ |
118 |
|
coverageVariable: opts.variable, |
119 |
|
embedSource: opts['embed-source'], |
120 |
|
noCompact: !opts.compact |
121 |
|
}); |
122 |
|
|
123 |
|
file = path.resolve(cmdArgs[0]); |
124 |
|
stats = fs.statSync(file); |
125 |
|
if (stats.isDirectory()) { |
126 |
|
if (!opts.output) { throw inputError.create('Need an output directory [-o <dir>] when input is a directory!'); } |
127 |
|
if (opts.output === file) { throw inputError.create('Cannot instrument into the same directory/ file as input!'); } |
128 |
|
mkdirp.sync(opts.output); |
129 |
|
filesFor({ |
130 |
|
root: file, |
131 |
|
includes: ['**/*.js'], |
132 |
|
excludes: opts.x || ['**/node_modules/**'], |
133 |
|
relative: true |
134 |
|
}, function (err, files) { |
135 |
|
if (err) { throw err; } |
136 |
|
processFiles(instrumenter, file, opts.output, files); |
137 |
|
}); |
138 |
|
} else { |
139 |
|
if (opts.output) { |
140 |
|
stream = fs.createWriteStream(opts.output); |
141 |
|
} else { |
142 |
|
stream = process.stdout; |
143 |
|
} |
144 |
|
stream.write(instrumenter.instrumentSync(fs.readFileSync(file, 'utf8'), file)); |
145 |
|
try { stream.end(); } catch (ex) {} |
146 |
|
} |
147 |
|
} |
148 |
|
}); |
149 |
|
|
150 |
1 |
module.exports = InstrumentCommand; |
151 |
|
|
152 |
|
|