diff --git a/tasks/parser.js b/tasks/parser.js index a0c19e47..252b8c26 100644 --- a/tasks/parser.js +++ b/tasks/parser.js @@ -1,39 +1,33 @@ -const childProcess = require('child_process'); +const { execFileWithInheritedOutput } = require('./util/exec-file'); +const { createRegisterAsyncTaskFn } = require('./util/async-grunt-task'); + +const OUTPUT_FILE = 'lib/handlebars/compiler/parser.js'; module.exports = function(grunt) { - grunt.registerTask('parser', 'Generate jison parser.', function() { - const done = this.async(); + const registerAsyncTask = createRegisterAsyncTaskFn(grunt); - let cmd = './node_modules/.bin/jison'; - - if (process.platform === 'win32') { - cmd = 'node_modules\\.bin\\jison.cmd'; - } - - const child = childProcess.spawn( - cmd, - ['-m', 'js', 'src/handlebars.yy', 'src/handlebars.l'], - { stdio: 'inherit' } - ); - child.on('exit', function(code) { - if (code != 0) { - grunt.fatal('Jison failure: ' + code); - done(); - return; - } - - const src = [ - 'src/parser-prefix.js', - 'handlebars.js', - 'src/parser-suffix.js' - ] - .map(grunt.file.read) - .join(''); - grunt.file.delete('handlebars.js'); - - grunt.file.write('lib/handlebars/compiler/parser.js', src); - grunt.log.writeln('Parser "lib/handlebars/compiler/parser.js" created.'); - done(); - }); + registerAsyncTask('parser', async () => { + await runJison(); + combineWithPrefixAndSuffix(); + grunt.log.writeln(`Parser "${OUTPUT_FILE}" created.`); }); + + async function runJison() { + await execFileWithInheritedOutput('jison', [ + '-m', + 'js', + 'src/handlebars.yy', + 'src/handlebars.l' + ]); + } + + function combineWithPrefixAndSuffix() { + const combinedParserSourceCode = + grunt.file.read('src/parser-prefix.js') + + grunt.file.read('handlebars.js') + + grunt.file.read('src/parser-suffix.js'); + + grunt.file.write(OUTPUT_FILE, combinedParserSourceCode); + grunt.file.delete('handlebars.js'); + } }; diff --git a/tasks/test-bin.js b/tasks/test-bin.js index 48c0ded8..8873c9f0 100644 --- a/tasks/test-bin.js +++ b/tasks/test-bin.js @@ -1,55 +1,45 @@ const childProcess = require('child_process'), fs = require('fs'), os = require('os'), - expect = require('chai').expect, - util = require('util'); - -const readFile = util.promisify(fs.readFile); -const execFile = util.promisify(childProcess.execFile); + expect = require('chai').expect; module.exports = function(grunt) { - grunt.registerTask( - 'test:bin', - wrapAsync(async function() { - const { stdout } = await execFileWithWin32Fallback('./bin/handlebars', [ - '-a', - 'spec/artifacts/empty.handlebars' - ]); + grunt.registerTask('test:bin', function() { + const stdout = executeBinHandlebars( + '-a', + 'spec/artifacts/empty.handlebars' + ); - const expectedOutput = await readFile( - './spec/expected/empty.amd.js', - 'utf-8' - ); + const expectedOutput = fs.readFileSync( + './spec/expected/empty.amd.js', + 'utf-8' + ); - const normalizedOutput = normalizeCrlf(stdout); - const normalizedExpectedOutput = normalizeCrlf(expectedOutput); - expect(normalizedOutput).to.equal(normalizedExpectedOutput); - }) - ); + const normalizedOutput = normalizeCrlf(stdout); + const normalizedExpectedOutput = normalizeCrlf(expectedOutput); - async function execFileWithWin32Fallback(command, args) { - // On Windows, the executable handlebars.js file cannot be run directly - if (os.platform() === 'win32') { - args.unshift(command); - command = process.argv[0]; - } - return execFile(command, args, { encoding: 'utf-8' }); - } - - function normalizeCrlf(string) { - if (string != null) { - return string.replace(/\r\n/g, '\n'); - } - return string; - } - - function wrapAsync(asyncFunction) { - return function() { - asyncFunction() - .catch(error => { - grunt.fatal(error); - }) - .finally(this.async()); - }; - } + expect(normalizedOutput).to.equal(normalizedExpectedOutput); + }); }; + +// helper functions + +function executeBinHandlebars(...args) { + if (os.platform() === 'win32') { + // On Windows, the executable handlebars.js file cannot be run directly + const nodeJs = process.argv[0]; + return execFilesSyncUtf8(nodeJs, ['./bin/handlebars'].concat(args)); + } + return execFilesSyncUtf8('./bin/handlebars', args); +} + +function execFilesSyncUtf8(command, args) { + return childProcess.execFileSync(command, args, { encoding: 'utf-8' }); +} + +function normalizeCrlf(string) { + if (typeof string === 'string') { + return string.replace(/\r\n/g, '\n'); + } + return string; +} diff --git a/tasks/test-mocha.js b/tasks/test-mocha.js index 9bed2c63..445a88cf 100644 --- a/tasks/test-mocha.js +++ b/tasks/test-mocha.js @@ -1,66 +1,36 @@ -const childProcess = require('child_process'); +const { execNodeJsScriptWithInheritedOutput } = require('./util/exec-file'); +const { createRegisterAsyncTaskFn } = require('./util/async-grunt-task'); module.exports = function(grunt) { - grunt.registerTask( - 'test:mocha', - promiseBasedTask(async () => forkAndWait('./spec/env/runner')) + const registerAsyncTask = createRegisterAsyncTaskFn(grunt); + + registerAsyncTask('test:mocha', async () => + execNodeJsScriptWithInheritedOutput('./spec/env/runner') ); - grunt.registerTask( - 'test:cov', - promiseBasedTask(async () => - forkAndWait( - 'node_modules/istanbul/lib/cli.js', - 'cover', - '--source-map', - '--', - './spec/env/runner.js' - ) - ) + registerAsyncTask('test:cov', async () => + execNodeJsScriptWithInheritedOutput('node_modules/istanbul/lib/cli.js', [ + 'cover', + '--source-map', + '--', + './spec/env/runner.js' + ]) ); - grunt.registerTask( - 'test:min', - promiseBasedTask(async () => forkAndWait('./spec/env/runner', '--min')) + registerAsyncTask('test:min', async () => + execNodeJsScriptWithInheritedOutput('./spec/env/runner', ['--min']) ); - grunt.registerTask( - 'test:check-cov', - promiseBasedTask(() => - forkAndWait( - 'node_modules/istanbul/lib/cli.js', - 'check-coverage', - '--statements', - '100', - '--functions', - '100', - '--branches', - '100', - '--lines 100' - ) - ) + registerAsyncTask('test:check-cov', async () => + execNodeJsScriptWithInheritedOutput('node_modules/istanbul/lib/cli.js', [ + 'check-coverage', + '--statements', + '100', + '--functions', + '100', + '--branches', + '100', + '--lines 100' + ]) ); - - function promiseBasedTask(asyncFunction) { - return function() { - asyncFunction() - .catch(error => { - grunt.fatal(error); - }) - .finally(this.async()); - }; - } - - async function forkAndWait(command, ...args) { - return new Promise((resolve, reject) => { - const child = childProcess.fork(command, args, { stdio: 'inherit' }); - child.on('close', code => { - if (code !== 0) { - reject(new Error(`Child process failed with exit-code ${code}`)); - } - }); - }); - } - - grunt.registerTask('test', ['test:bin', 'test:cov', 'test:check-cov']); }; diff --git a/tasks/util/async-grunt-task.js b/tasks/util/async-grunt-task.js new file mode 100644 index 00000000..20360570 --- /dev/null +++ b/tasks/util/async-grunt-task.js @@ -0,0 +1,13 @@ +module.exports = { createRegisterAsyncTaskFn }; + +function createRegisterAsyncTaskFn(grunt) { + return function registerAsyncTask(name, asyncFunction) { + grunt.registerTask(name, function() { + asyncFunction() + .catch(error => { + grunt.fatal(error); + }) + .finally(this.async()); + }); + }; +} diff --git a/tasks/util/exec-file.js b/tasks/util/exec-file.js new file mode 100644 index 00000000..66fd8e92 --- /dev/null +++ b/tasks/util/exec-file.js @@ -0,0 +1,51 @@ +const childProcess = require('child_process'); +const fs = require('fs'); +const path = require('path'); + +module.exports = { + execNodeJsScriptWithInheritedOutput, + execFileWithInheritedOutput +}; + +async function execNodeJsScriptWithInheritedOutput(command, args) { + return new Promise((resolve, reject) => { + const child = childProcess.fork(command, args, { stdio: 'inherit' }); + child.on('close', code => { + if (code !== 0) { + reject(new Error(`Child process failed with exit-code ${code}`)); + } + resolve(); + }); + }); +} + +async function execFileWithInheritedOutput(command, args) { + return new Promise((resolve, reject) => { + const resolvedCommand = preferLocalDependencies(command); + const child = childProcess.spawn(resolvedCommand, args, { + stdio: 'inherit' + }); + child.on('exit', code => { + if (code !== 0) { + reject(new Error(`Child process failed with exit-code ${code}`)); + } + resolve(); + }); + }); +} + +function preferLocalDependencies(command) { + const localCandidate = resolveLocalCandidate(command); + + if (fs.existsSync(localCandidate)) { + return localCandidate; + } + return command; +} + +function resolveLocalCandidate(command) { + if (process.platform === 'win32') { + return path.join('node_modules', '.bin', command + '.cmd'); + } + return path.join('node_modules', '.bin', command); +}