diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..62f8049 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,26 @@ +{ + "env": { + "node": true, + "mocha": true + }, + "rules": { + "strict": 2, + "quotes": [2, "single", "avoid-escape"], + "indent": ["error", 2, {"SwitchCase": 1}], + "eol-last": 2, + "no-shadow": 2, + "dot-notation": 2, + "dot-location": [2, "property"], + "comma-dangle": [2, "never"], + "no-unused-vars": 2, + "keyword-spacing": 2, + "no-multi-spaces": 2, + "no-process-exit": 0, + "consistent-return": 0, + "space-before-blocks": 2, + "no-use-before-define": 0, + "no-underscore-dangle": 0, + "no-unused-expressions": 2, + "space-before-function-paren": 2 + } +} diff --git a/.jshintrc b/.jshintrc deleted file mode 100644 index 16957d2..0000000 --- a/.jshintrc +++ /dev/null @@ -1,19 +0,0 @@ -{ - "node": true, - "curly": true, - "undef": true, - "mocha": true, - "indent": 2, - "maxlen": 80, - "freeze": true, - "eqeqeq": true, - "eqnull": true, - "strict": true, - "unused": true, - "newcap": false, - "maxdepth": 3, - "quotmark": "single", - "funcscope": true, - "laxbreak" : true, - "maxstatements": 50 -} diff --git a/README.md b/README.md index c917cc2..7b75919 100644 --- a/README.md +++ b/README.md @@ -9,25 +9,40 @@ Simple repl for gulp compatible with gulp#3.x and the future gulp#4.x. ```js // gulpfile example var gulp = require('gulp'); -gulp.repl = require('gulp-repl')(gulp); +var repl = require('gulp-repl'); + +gulp.task('repl-start', function (cb) { + gulp.repl = repl.start(gulp); +}); + +gulp.task('repl-stop', function (cb) { + if (gulp.repl) { + gulp.repl.close(); // same as nodejs.org/api/readline.html#readline_rl_close + } + cb(); +}); + gulp.task('foo', function (cb) { - // do foo stuff - cb(); + // do foo stuff + cb(); }); gulp.task('bar', function (cb) { - // do bar stuff - cb(); + // do bar stuff + cb(); }); gulp.task('default'); +``` + +Then, on your terminal write: -// same as writing foo bar on the command line -gulp.repl.emit('line', 'foo bar'); +``` +gulp repl-start ``` -Then, on your terminal, you'll have a repl +and you'll have a repl with: 1. Press Enter to see the prompt 1. write the tasks you want to run @@ -48,9 +63,67 @@ foo bar default ### API -The module exports a `readline` interface. +The module exports a function + +```js +var repl = require('gulp-repl'); +``` + +with the following methods + +#### repl.add + +```js +function add(Gulp gulp) +``` + +Adds the `gulp` instance tasks for the REPL and _returns_ the module again. + +#### repl.remove + +```js +function remove(Gulp gulp) +``` + +Removes the `gulp` instance tasks from the REPL and _returns_ the module again. + +#### repl.reset + +```js +function reset() +``` + +Removes all of the previously added instances and _returns_ the module again. + +#### repl.get + +```js +function get(Gulp gulp) +``` + +Takes a `gulp` instance as argument + +_returns_ +- `null` if the `gulp` instance wasn't stored yet +- all of the stored instances if no arguments are given +- metadata stored for the given `gulp` instance if was already stored + +#### repl.start + +```js +function start(Gulp gulp) +``` + +Takes a `gulp` instance as argument + +Adds the `gulp` instance tasks for the REPL. + +Starts a REPL listening on `stdin` and writing on `stdout`. Each of the commands typed to the REPL are looked up in each of the instances given on `add`. + +_returns_ a `readline.Interface` instance. + +[See node's core module `readline` documentation about the `readline.Interface`](https://nodejs.org/api/readline.html). -For more information [see node's core module `readline` documentation](https://nodejs.org/api/readline.html). ### install @@ -58,11 +131,38 @@ For more information [see node's core module `readline` documentation](https://n $ npm install --save-dev gulp-repl ``` +## Changelog + +[v2.0.1][v2.0.1]: +- test: small fix to use `repl.start` instead of the `module.exports` +- docs: remove docs of exporting a function +- docs: small fix on the docs +- version bump + +[v2.0.0][v2.0.0]: +- docs: add new api docs +- test: split test into files for each api method +- dev: separate module into add, get, remove, reset and start + +[v1.1.2][v1.1.2]: + +- docs: add changelog +- next_release: patch release +- fix: `gulp.parallel` as task runner when `gulp.start` is undefined + +[v1.1.1][v1.1.1]: + +- fix: make the repl prompt after not found tasks +- fix: line end matches + +[v1.1.0][v1.1.0]: +- manage multiple gulp instances + ### license The MIT License (MIT) -Copyright (c) 2015-2016 Javier Carrillo +Copyright (c) 2015-present Javier Carrillo Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: @@ -77,3 +177,10 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI [b-build]: https://travis-ci.org/stringparser/gulp-repl.svg?branch=master [b-version]: http://img.shields.io/npm/v/gulp-repl.svg?style=flat-square [badge-downloads]: http://img.shields.io/npm/dm/gulp-repl.svg?style=flat-square + +[v2.0.1]: https://github.com/stringparser/gulp-repl/commit/4420f55db8f9a4887e5a8bd82976a8930e12fb50 + +[v2.0.0]: https://github.com/stringparser/gulp-repl/commit/be44875927a42d8f08dcafa7984db0bfc423e0a3 +[v1.1.2]: https://github.com/stringparser/gulp-repl/commit/572df8ce7cd9d4edd3a2190de021381671a295f0 +[v1.1.1]: https://github.com/stringparser/gulp-repl/commit/6f4655ca1a667ca04d2a668a175055f9b4437d65 +[v1.1.0]: https://github.com/stringparser/gulp-repl/commit/71a2301233a92d68dbfd7e7a1493a38be72d0a0e diff --git a/index.js b/index.js index fbb2283..1c96a75 100644 --- a/index.js +++ b/index.js @@ -2,90 +2,112 @@ var util = require('./lib/util'); -exports = module.exports = gulpRepl; -exports.instances = []; - -/** - * create a readline interface -**/ -var repl = require('readline').createInterface({ - input: process.stdin, - output: process.stdout, - completer: function onCompletion(line){ - return util.completer(line, exports.instances); +var repl = null; +var instances = []; + +exports = module.exports = {}; + +// get the instance properties used by the REPL +exports.get = function (gulp) { + if (!arguments.length) { + return instances.concat(); } -}); - -/** - * queue tasks when line is not empty -**/ -repl.on('line', function onLine(input){ - var line = input.trim(); - if(!line){ return repl.prompt(); } - - var queue = { - found: [], - notFound: line.split(/[ ]+/) - }; - - exports.instances.forEach(function(inst){ - var tasks = util.getQueue(queue.notFound.join(' '), inst.tasks); - if(tasks.found.length){ - queue.found.push({ - inst: inst, - tasks: tasks.found - }); + + var length = instances.length; + + for (var index = 0; index < length; ++index) { + var instance = instances[index] || {}; + if (instance.gulp === gulp) { + return instance; } - queue.notFound = tasks.notFound; - }); + } - if(queue.notFound.length){ - var plural = queue.notFound.length > 1; + return null; +}; - console.log(' `%s` task%s %s not defined yet', - queue.notFound.join(', '), - plural ? 's' : '', - plural ? 'are' : 'is' - ); +// add the given instance to the REPL lookup +exports.add = function (_gulp_) { + if (_gulp_ && !this.get(_gulp_)) { + var gulp = util.getGulp(_gulp_); - return repl.prompt(); + instances.push({ + gulp: gulp, + index: instances.length, + tasks: util.getTasks(gulp), + runner: gulp.start || gulp.parallel + }); } + return this; +}; + +// reset the instances array +exports.reset = function () { + instances = []; + return this; +}; + +// remove the instance from the instances array +exports.remove = function (_gulp_) { + var instance = this.get(_gulp_); + if (instance) { + instances.splice(instance.index, 1); + } + return this; +}; + +// create a readline instance if there is none +exports.start = function (_gulp_) { + this.add(_gulp_); - queue.found.forEach(function(found){ - var result = found.inst.runner.apply(found.inst.gulp, found.tasks); - if(typeof result === 'function'){ - result(); // gulp#4.0 + // only create one repl listening on stdin + if (repl) { return repl; } + + repl = require('readline').createInterface({ + input: process.stdin, + output: process.stdout, + completer: function onComplete (line) { + return util.completer(line, instances); + } + }); + + // queue tasks when line is not empty + repl.on('line', function onLine (input) { + var line = input.trim(); + if (!line) { return repl.prompt(); } + + var queue = { + found: [], + notFound: line.split(/[ ]+/) + }; + + instances.forEach(function (inst) { + var tasks = util.getQueue(queue.notFound.join(' '), inst.tasks); + if (tasks.found.length) { + queue.found.push({ inst: inst, tasks: tasks.found }); + } + queue.notFound = tasks.notFound; + }); + + if (queue.notFound.length) { + console.log(' `%s` not found', queue.notFound.join(' ')); + return repl.prompt(); } + + queue.found.forEach(function (found) { + var result = found.inst.runner.apply(found.inst.gulp, found.tasks); + if (typeof result === 'function') { + result(); // gulp#4.0 + } + }); + + return this; }); -}); - -/** - * exit on SIGINT with a timestamp -**/ -repl.on('SIGINT', function onSIGINT(){ - process.stdout.write('\n'); - console.log(new Date()); - process.exit(0); -}); - -/** - * add the given gulp instance to the instances array -**/ -function gulpRepl(_gulp_){ - var gulp = util.getGulp(_gulp_); - - var inInstances = Boolean( - exports.instances.filter(function(instance){ - return instance.gulp === gulp; - }).length - ); - if(inInstances){ return repl; } - - exports.instances.push({ - gulp: gulp, - tasks: util.getTasks(gulp), - runner: gulp.start + + // exit on SIGINT + repl.on('SIGINT', function onSIGINT () { + process.stdout.write('\n'); + process.exit(0); }); return repl; -} +}; diff --git a/lib/util.js b/lib/util.js index 840b040..0c62a95 100644 --- a/lib/util.js +++ b/lib/util.js @@ -1,14 +1,15 @@ 'use strict'; -exports = module.exports = {}; +var uniq = require('lodash.uniq'); -exports.uniq = require('lodash.uniq'); +exports = module.exports = {}; -exports.getGulp = function(gulp){ - if(!gulp || typeof gulp.task !== 'function'){ +// return a gulp-like instance, require gulp if not given +exports.getGulp = function (gulp) { + if (!gulp || typeof gulp.task !== 'function') { try { gulp = require('gulp'); - } catch (err){ + } catch (err) { console.log('gulp is not installed locally'); console.log('try `npm install gulp`'); process.exit(1); @@ -17,7 +18,8 @@ exports.getGulp = function(gulp){ return gulp; }; -exports.getTasks = function(gulp){ +// get the tasks for the given instance +exports.getTasks = function (gulp) { var tasks = { obj: ( (gulp._registry && gulp._registry._tasks) || // gulp#4 @@ -26,12 +28,12 @@ exports.getTasks = function(gulp){ ) }; - if(typeof (gulp.tasks && gulp.tasks.get) === 'function'){ - tasks.get = function(line){ + if (typeof (gulp.tasks && gulp.tasks.get) === 'function') { + tasks.get = function (line) { return gulp.tasks.get(line); }; } else { - tasks.get = function(line, name){ + tasks.get = function (line, name) { return ( (tasks.obj[line] && {match: line}) || (tasks.obj[name] && {match: name}) @@ -42,14 +44,15 @@ exports.getTasks = function(gulp){ return tasks; }; -exports.getQueue = function(line, tasks){ +// get an object with of found/not found tasks to run +exports.getQueue = function (line, tasks) { var queue = {found: [], notFound: []}; - while(line.length){ + while (line.length) { var name = /(^\S+)/.exec(line).pop(); var task = tasks.get(line, name); - if(task && task.match){ + if (task && task.match) { queue.found.push(task.match); line = line.slice(task.match.length).trim(); } else { @@ -61,19 +64,20 @@ exports.getQueue = function(line, tasks){ return queue; }; -exports.completer = function(line, instances){ +// return all instances completion for the given line +exports.completer = function (line, instances) { var match = line.match(/([\s]+|^)\S+$/); - if(match){ + if (match) { line = line.slice(match.index, line.length).trim(); } var completion = []; - instances.forEach(function (instance){ + instances.forEach(function (instance) { var matches = exports.getQueue(line, instance.tasks); - if(!matches.found.length){ - Object.keys(instance.tasks.obj).forEach(function(name){ + if (!matches.found.length) { + Object.keys(instance.tasks.obj).forEach(function (name) { matches.found.push.apply(matches.found, (name.match(/\(([^(]+)\)/) || [name]).pop().split('|') ); @@ -82,7 +86,7 @@ exports.completer = function(line, instances){ completion.push.apply(completion, matches.found); }); - var hits = exports.uniq(completion).filter(function(elem){ + var hits = uniq(completion).filter(function (elem) { return !elem.indexOf(line); }); diff --git a/package.json b/package.json index 73a063d..bda67fc 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "gulp-repl", "author": "Javier Carrillo ", - "version": "1.1.1", + "version": "2.0.3", "license": "MIT", "repository": "https://github.com/stringparser/gulp-repl", "description": "a simple repl for gulp", @@ -14,12 +14,12 @@ ], "devDependencies": { "gulp": "^3.x", - "jshint": "*", + "eslint": "^2.x.x", "mocha": "*", "should": "*" }, "scripts": { - "lint": "jshint lib index.js --exclude ./node_modules", + "lint": "eslint lib examples test index.js", "test": "npm run-script lint && mocha test" }, "dependencies": { diff --git a/test/add.js b/test/add.js new file mode 100644 index 0000000..fa47126 --- /dev/null +++ b/test/add.js @@ -0,0 +1,32 @@ +'use strict'; + +exports = module.exports = function (api, repl, Gulp, should) { + + it('should return the module', function () { + var gulp = new Gulp(); + + api.add(gulp).should.be.eql(api); + }); + + it('should add to the instances store', function () { + var gulp = new Gulp(); + + should(api.get(gulp)).be.eql(null); + + api.add(gulp); + + should(api.get().filter(function (instance) { + return instance.gulp === gulp; + }).length).be.eql(1); + }); + + it('should not add an instance more than once', function () { + var gulp = new Gulp(); + + api.reset(); + + '1234567890'.split('').forEach(function () { + api.add(gulp).get().length.should.be.eql(1); + }); + }); +}; diff --git a/test/exports.js b/test/exports.js new file mode 100644 index 0000000..d9b35d8 --- /dev/null +++ b/test/exports.js @@ -0,0 +1,135 @@ +'use strict'; + +exports = module.exports = function (api, repl, Gulp, should) { + + it('repl should be a readline Interface', function () { + var readline = require('readline'); + repl.constructor.should.be.eql(readline.Interface); + }); + + it('emit dispatches registered tasks', function (done) { + var pile = []; + var gulp = new Gulp(); + + api.add(gulp); + + gulp.task('one', function (cb) { + pile.push('one'); + if (pile.length > 1) { end(); } + cb(); + }); + + gulp.task('two', function (cb) { + pile.push('two'); + if (pile.length > 1) { end(); } + cb(); + }); + + repl.emit('line', 'one two'); + + function end () { + pile.should.containDeep(['one', 'two']); + done(); + } + }); + + it('undefined tasks should not run', function (done) { + var gulp = new Gulp(); + + api.add(gulp); + + gulp.task('one', function (cb) { + done(new Error('should not run failed')); + setTimeout(cb, 0); + }); + + should.exist(gulp.tasks || gulp._registry._tasks); + + var prompt = repl.prompt; + repl.prompt = function () { + repl.prompt = prompt; + prompt.apply(repl, arguments); + done(); + }; + + repl.emit('line', 'not found task'); + }); + + it('should handle more than one instance', function (done) { + var pile = []; + + var gulp1 = new Gulp(); + var gulp2 = new Gulp(); + + api.add(gulp1).add(gulp2); + + should(gulp1 !== gulp2).be.eql(true); + + api.get(gulp1).gulp.should.be.eql(gulp1); + api.get(gulp2).gulp.should.be.eql(gulp2); + + gulp1.task('three', function (cb) { + pile.push('three'); + if (pile.length > 1) { end(); } + cb(); + }); + + gulp2.task('four', function (cb) { + pile.push('four'); + if (pile.length > 1) { end(); } + cb(); + }); + + repl.emit('line', 'three four'); + + function end () { + pile.should.containDeep(['three', 'four']); + done(); + } + }); + + it('should run found tasks indenpently of instances', function (done) { + var pile = []; + var gulp1 = new Gulp(); + var gulp2 = new Gulp(); + + api.reset(); + + api.add(gulp1); + api.add(gulp2); + + gulp1.task('one', function (cb) { + pile.push('one'); + if (pile.length > 1) { end(); } + cb(); + }); + + gulp2.task('two', function (cb) { + pile.push('two'); + if (pile.length > 1) { end(); } + cb(); + }); + + repl.emit('line', 'one two'); + + function end () { + pile.should.containDeep(['one', 'two']); + done(); + } + }); + + it('should prompt after not found tasks', function (done) { + var gulp = new Gulp(); + + api.add(gulp); + + var prompt = repl.prompt; + repl.prompt = function () { + repl.prompt = prompt; + prompt.apply(repl, arguments); + done(); + }; + + repl.emit('line', 'not found task'); + }); +}; diff --git a/test/get.js b/test/get.js new file mode 100644 index 0000000..9130cb8 --- /dev/null +++ b/test/get.js @@ -0,0 +1,26 @@ +'use strict'; + +exports = module.exports = function (api, repl, Gulp, should) { + + it('should return null if the instance is not set', function () { + var gulp = new Gulp(); + should(api.get(gulp)).be.eql(null); + }); + + it('should return an object with the instance set', function () { + var gulp = new Gulp(); + + api.add(gulp).get(gulp).gulp.should.be.eql(gulp); + }); + + it('should return all instances when called with no arguments', function () { + var gulp1 = new Gulp(); + var gulp2 = new Gulp(); + + api.add(gulp1).add(gulp2); + + api.get().map(function (stored) { + return stored.gulp; + }).should.containDeep([gulp1, gulp2]); + }); +}; diff --git a/test/index.js b/test/index.js index a13d931..f958d61 100644 --- a/test/index.js +++ b/test/index.js @@ -1,127 +1,19 @@ 'use strict'; -var gulp = require('gulp'); -var should = require('should'); -var readline = require('readline'); - -var gulpRepl = require('../.'); -var repl = gulpRepl(gulp); - -it('repl should be a readline Interface', function(){ - repl.constructor.should.be.eql(readline.Interface); -}); - -it('should dispatch registered tasks', function(done){ - var pile = []; - - gulp.task('one', function(cb){ - pile.push('one'); - if(pile.length > 1){ end(); } - cb(); - }); - - gulp.task('two', function(cb){ - pile.push('two'); - if(pile.length > 1){ end(); } - cb(); - }); - - repl.emit('line', 'one two'); - function end(){ - pile.should.containDeep(['one', 'two']); - done(); - } -}); - -it('undefined tasks should not run', function(done){ - should.exist(gulp.tasks || gulp._registry._tasks); - var prompt = repl.prompt; - - repl.prompt = function(){ - repl.prompt = prompt; - prompt.apply(repl, arguments); - done(); - }; - - repl.emit('line', 'not found task'); -}); - -it('should handle more than one instance', function (done){ - var pile = []; - var gulp2 = new gulp.constructor(); - - gulpRepl(gulp2); - - gulp.task('three', function(cb){ - pile.push('three'); - if(pile.length > 1){ end(); } - cb(); - }); - - gulp2.task('four', function(cb){ - pile.push('four'); - if(pile.length > 1){ end(); } - cb(); - }); +var fs = require('fs'); +var Gulp = require('gulp').constructor; +var should = require('should'); - repl.emit('line', 'three four'); +var api = require('../.'); +var repl = api.start(); - function end(){ - pile.should.containDeep(['three', 'four']); - done(); +fs.readdirSync(__dirname).forEach(function (file) { + if (file === 'index.js') { + return; } -}); - -it('should not add an instance more than once', function (){ - var length = gulpRepl.instances.length; - var gulp3 = new gulp.constructor(); - '1234567890'.split('').forEach(function(){ - gulpRepl(gulp3); - gulpRepl.instances.length.should.be.eql(length + 1); - }); -}); - -it('should run found tasks indenpently of instances', function (done){ - var gulp1 = new gulp.constructor(); - var gulp2 = new gulp.constructor(); - gulpRepl.instances = []; - gulpRepl(gulp1); - gulpRepl(gulp2); - - var pile = []; - gulp1.task('one', function(cb){ - pile.push('one'); - if(pile.length > 1){ end(); } - cb(); + describe(file.split('.')[0], function () { + require('./' + file)(api, repl, Gulp, should); }); - - gulp2.task('two', function(cb){ - pile.push('two'); - if(pile.length > 1){ end(); } - cb(); - }); - - repl.emit('line', 'one two'); - - function end(){ - pile.should.containDeep(['one', 'two']); - done(); - } -}); - -it('should prompt after not found tasks', function (done){ - var gulp3 = new gulp.constructor(); - gulpRepl(gulp3); - - var prompt = repl.prompt; - - repl.prompt = function(){ - repl.prompt = prompt; - prompt.apply(repl, arguments); - done(); - }; - - repl.emit('line', 'not found task'); }); diff --git a/test/remove.js b/test/remove.js new file mode 100644 index 0000000..4be3576 --- /dev/null +++ b/test/remove.js @@ -0,0 +1,18 @@ +'use strict'; + +exports = module.exports = function (api, repl, Gulp, should) { + + it('should delete the instance from the instances store', function () { + var gulp = new Gulp(); + + should(api.get(gulp)).be.eql(null); + + api.add(gulp); + + should(api.get(gulp).gulp).be.eql(gulp); + + api.remove(gulp); + + should(api.get(gulp)).be.eql(null); + }); +}; diff --git a/test/reset.js b/test/reset.js new file mode 100644 index 0000000..94df3d7 --- /dev/null +++ b/test/reset.js @@ -0,0 +1,8 @@ +'use strict'; + +exports = module.exports = function (/*api, repl, Gulp */) { + + it('should reset all instances set', function () { + + }); +};