From e62147da474f0792fc3b63af96e11540157bf0d3 Mon Sep 17 00:00:00 2001 From: kuakman <3dimentionar@gmail.com> Date: Sun, 26 Feb 2017 16:44:03 -0800 Subject: [PATCH 01/39] Checkpoint - Updated README.md with github stars badge. --- README.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/README.md b/README.md index 377b058..bbd5e63 100644 --- a/README.md +++ b/README.md @@ -3,10 +3,7 @@ [![Build Status](https://travis-ci.org/nahuelio/squarebox.svg?branch=master)](https://travis-ci.org/nahuelio/squarebox) [![Coverage Status](https://coveralls.io/repos/github/nahuelio/squarebox/badge.svg)](https://coveralls.io/github/nahuelio/squarebox) - -``` -Badges -``` +[![GitHub stars](https://img.shields.io/github/stars/nahuelio/squarebox.svg?label=Stars)]() #### Introduction From 8e298499112c78e4596300b037f24a835f06a686 Mon Sep 17 00:00:00 2001 From: kuakman <3dimentionar@gmail.com> Date: Sun, 26 Feb 2017 17:13:26 -0800 Subject: [PATCH 02/39] Updated Badges. --- README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index bbd5e63..f16e788 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,9 @@ [![Build Status](https://travis-ci.org/nahuelio/squarebox.svg?branch=master)](https://travis-ci.org/nahuelio/squarebox) [![Coverage Status](https://coveralls.io/repos/github/nahuelio/squarebox/badge.svg)](https://coveralls.io/github/nahuelio/squarebox) -[![GitHub stars](https://img.shields.io/github/stars/nahuelio/squarebox.svg?label=Stars)]() +[![Version](https://img.shields.io/badge/Version-1.0.0-blue.svg?style=flat)]() +[![Version](https://img.shields.io/badge/License-MIT-blue.svg?style=flat)](http://www.opensource.org/licenses/mit-license.php) +[![GitHub stars](https://img.shields.io/github/stars/nahuelio/squarebox.svg?style=social&label=Stars)]() #### Introduction @@ -26,5 +28,5 @@ TODO ``` TODO ``` - -Built with :heart: by [NahuelIO](http://nahuel.io) - [MIT License](http://www.opensource.org/licenses/mit-license.php) +[![forthebadge](http://forthebadge.com/images/badges/built-with-love.svg)](http://nahuel.io) +by [Nahuel IO](http://nahuel.io) From c8e87ab097febac631b32eed1d5d25ec3060daba Mon Sep 17 00:00:00 2001 From: kuakman <3dimentionar@gmail.com> Date: Sun, 26 Feb 2017 20:30:46 -0800 Subject: [PATCH 03/39] util: Checkpoint - Updated package.json scripts for release. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7ac680d..63f0810 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "coverage": "istanbul cover ./node_modules/.bin/_mocha -- --opts mocha.opts test/commands", "coveralls": "cat ./coverage/lcov.info | node node_modules/.bin/coveralls", "docs": "esdoc", - "build": "node register" + "build": "node release" }, "files": [ "bin", From 6877b8871e3eed468ac43adc9477c37f7ddca585 Mon Sep 17 00:00:00 2001 From: kuakman <3dimentionar@gmail.com> Date: Wed, 1 Mar 2017 11:36:38 -0800 Subject: [PATCH 04/39] util: checkpoint - Fixed Code Coverage Bug by using nyc dependency and babel-plugin-istanbul. Updated coverage npm script. Configuration added into package.json for nyc and babelrc. --- .babelrc | 7 ++++++- .gitignore | 1 + .istanbul.yml | 1 + mocha.opts | 1 + package.json | 18 +++++++++++++----- src/commands/util/adt/stack.es6 | 9 +++++++-- test/commands/util/adt/stack.spec.es6 | 6 ++++++ test/specs/config-basic.json | 15 ++++++++++++--- 8 files changed, 47 insertions(+), 11 deletions(-) diff --git a/.babelrc b/.babelrc index 2aec121..3477908 100644 --- a/.babelrc +++ b/.babelrc @@ -5,5 +5,10 @@ "root": ["./src"], "alias": { "commands": "./commands" } }] - ] + ], + "env": { + "test": { + "plugins": ["istanbul"] + } + } } diff --git a/.gitignore b/.gitignore index 6ad53d1..033cbf2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ # Project /bin lib +.nyc_output coverage node_modules diff --git a/.istanbul.yml b/.istanbul.yml index 10e292d..02b328d 100644 --- a/.istanbul.yml +++ b/.istanbul.yml @@ -1,5 +1,6 @@ instrumentation: root: src + include-all-sources: true extensions: - .es6 - .js diff --git a/mocha.opts b/mocha.opts index 8a086db..bc6d910 100644 --- a/mocha.opts +++ b/mocha.opts @@ -1,4 +1,5 @@ --require ./test/global +--require babel-register --recursive --ui bdd --timeout 500 diff --git a/package.json b/package.json index 63f0810..e481931 100644 --- a/package.json +++ b/package.json @@ -9,11 +9,18 @@ "scripts": { "test": "_mocha --opts mocha.opts --watch test/commands", "it": "_mocha --opts mocha.opts test/integration", - "coverage": "istanbul cover ./node_modules/.bin/_mocha -- --opts mocha.opts test/commands", + "coverage": "cross-env NODE_ENV=test nyc --reporter=lcov --reporter=html --reporter=text mocha --opts mocha.opts test/commands", "coveralls": "cat ./coverage/lcov.info | node node_modules/.bin/coveralls", "docs": "esdoc", "build": "node release" }, + "nyc": { + "require": [ + "babel-register" + ], + "sourceMap": true, + "instrument": false + }, "files": [ "bin", "lib", @@ -30,18 +37,19 @@ "license": "MIT", "devDependencies": { "babel-cli": "6.23.0", - "babel-core": "6.23.1", + "babel-plugin-istanbul": "4.0.0", "babel-plugin-module-resolver": "2.5.0", "babel-preset-es2015": "6.22.0", "babel-preset-stage-2": "6.22.0", "babel-register": "6.23.0", - "chai": "^3.5.0", - "coveralls": "^2.11.16", + "chai": "3.5.0", + "coveralls": "2.11.16", + "cross-env": "3.1.4", "esdoc": "0.5.2", "esdoc-es7-plugin": "0.0.3", - "istanbul": "1.1.0-alpha.1", "mocha": "2.5.3", "mocha-lcov-reporter": "1.3.0", + "nyc": "10.1.2", "sinon": "1.17.7" }, "dependencies": { diff --git a/src/commands/util/adt/stack.es6 b/src/commands/util/adt/stack.es6 index 97f7484..d10b180 100644 --- a/src/commands/util/adt/stack.es6 +++ b/src/commands/util/adt/stack.es6 @@ -2,6 +2,8 @@ * @module commands.util.adt.Stack * @author Patricio Ferreira <3dimentionar@gmail.com> **/ +'use strict'; + import _ from 'underscore'; import extend from 'extend'; import Collection from 'commands/util/adt/collection'; @@ -21,7 +23,7 @@ import Collection from 'commands/util/adt/collection'; * mystack.pop(); // Outputs { name: 'one' } of MyClass, removing the element * @extends commands.util.adt.Collection **/ -export default class Stack extends Collection { +class Stack extends Collection { /** * Constructor @@ -31,7 +33,8 @@ export default class Stack extends Collection { * @return {commands.util.adt.Stack} **/ constructor(initial = [], opts = {}) { - return super(initial, opts); + super(initial, opts); + return this; } /** @@ -107,3 +110,5 @@ export default class Stack extends Collection { } } + +export default Stack; diff --git a/test/commands/util/adt/stack.spec.es6 b/test/commands/util/adt/stack.spec.es6 index 0876ebc..02d1987 100644 --- a/test/commands/util/adt/stack.spec.es6 +++ b/test/commands/util/adt/stack.spec.es6 @@ -117,6 +117,12 @@ describe('commands.util.adt.Stack', function() { assert.equal(1, expPos); }); + it('Should NOT search (invalid element)', () => { + const exp = Stack.new(['a','b','c']); + const expPos = exp.search(); + assert.equal(-1, expPos); + }); + it('Should search and retrieve -1 position (element not found)', () => { const newCommand = Command.new({ env: 'production' }); const exp = Stack.new([{ env: 'staging' }, newCommand], { interface: Command }); diff --git a/test/specs/config-basic.json b/test/specs/config-basic.json index 06b89cc..afa99e1 100644 --- a/test/specs/config-basic.json +++ b/test/specs/config-basic.json @@ -7,8 +7,17 @@ "libraries": "libs" } }, - "target": { - "destination": "./dist", + "target": [{ + "destination": "./dist/global", "format": "ifie" - } + }, { + "destination": "./dist/cjs", + "format": "cjs" + }, { + "destination": "./dist/umd", + "format": "umd" + }, { + "destination": "./dist/amd", + "format": "amd" + }] } From 694b6f5ac85c8e9a42b1be182f36da88bf583b33 Mon Sep 17 00:00:00 2001 From: kuakman <3dimentionar@gmail.com> Date: Wed, 1 Mar 2017 18:43:15 -0800 Subject: [PATCH 05/39] util: checkpoint - added more unit test coverage on commands.util.adt.Collection. --- src/commands/util/adt/collection.es6 | 13 +- test/commands/util/adt/collection.spec.es6 | 149 ++++++++++++++++----- 2 files changed, 121 insertions(+), 41 deletions(-) diff --git a/src/commands/util/adt/collection.es6 b/src/commands/util/adt/collection.es6 index 992b8e5..8b85139 100644 --- a/src/commands/util/adt/collection.es6 +++ b/src/commands/util/adt/collection.es6 @@ -29,6 +29,9 @@ export default class Collection extends EventEmitter { /** * Constructor + * @FIXME: Reminder: Important fix the underscore aggregation: + * Not do it per instance (it adds overhead), + * instead apply it to the prototype of this class! * @public * @param [initial = []] {Array} Initial Array * @param [opts = {}] {Object} collection options @@ -112,6 +115,7 @@ export default class Collection extends EventEmitter { * @return {Any} **/ add(element, opts = {}) { + if(!this._valid(element)) return null; this._collection.push(this._new(element, opts)); this._fire(Collection.events.add, opts, element); return element; @@ -126,8 +130,9 @@ export default class Collection extends EventEmitter { * @return {commands.util.adt.Collection} **/ addAll(col = [], opts = {}) { - _.each(col, (e) => this.add(e, extend(true, opts, { silent: true }))); - return this._fire(Collection.events.addall, opts); + if(!_.isArray(col) || col.length === 0) return this; + let added = _.map(col, (e) => this.add(e, extend(true, {}, opts, { silent: true }))); + return this._fire(Collection.events.addall, opts, added); } /** @@ -148,7 +153,7 @@ export default class Collection extends EventEmitter { **/ contains(element) { if(!this._valid(element)) return false; - return this.some((e) => _.isEqual(e, element)); + return this.some((e) => _.isEqual((this.hasInterface() && e.toJSON) ? e.toJSON() : e, element)); } /** @@ -243,7 +248,7 @@ export default class Collection extends EventEmitter { * @return {commands.util.adt.Collection} **/ reset(opts = {}) { - // TODO + this._collection = []; return this._fire(Collection.events.reset, opts); } diff --git a/test/commands/util/adt/collection.spec.es6 b/test/commands/util/adt/collection.spec.es6 index ffebec3..766d659 100644 --- a/test/commands/util/adt/collection.spec.es6 +++ b/test/commands/util/adt/collection.spec.es6 @@ -16,6 +16,7 @@ describe('commands.util.adt.Collection', function() { }); afterEach(() => { + this.mockCollection.verify(); this.sandbox.restore(); delete this.mockCollection; }); @@ -62,8 +63,6 @@ describe('commands.util.adt.Collection', function() { assert.equal(3, exp.size()); assert.equal(toSet[1], exp.get(1)); - - this.mockCollection.verify(); }); it('Should set new elements (without Event)', () => { @@ -80,8 +79,6 @@ describe('commands.util.adt.Collection', function() { assert.equal(3, exp.size()); assert.equal(toSet[1], exp.get(1)); - - this.mockCollection.verify(); }); it('Should NOT set new elements', () => { @@ -90,8 +87,6 @@ describe('commands.util.adt.Collection', function() { assert.instanceOf(exp.set(), Collection); // undefined assert.instanceOf(exp.set({ option: 'notAnArray' }), Collection); // not an array - - this.mockCollection.verify(); }); }); @@ -109,8 +104,6 @@ describe('commands.util.adt.Collection', function() { exp.add(toAdd); assert.isFalse(exp.isEmpty()); assert.instanceOf(exp.get(0), Command); - - this.mockCollection.verify(); }); it('Should add a new element (without Event)', () => { @@ -121,112 +114,194 @@ describe('commands.util.adt.Collection', function() { exp.add(toAdd, { silent: true }); assert.isFalse(exp.isEmpty()); assert.instanceOf(exp.get(0), Command); - - this.mockCollection.verify(); }); - it('Should NOT add a new element', () => {}); + it('Should NOT add a new element', () => { + const exp = Collection.new(); + const expEmit = this.mockCollection.expects('emit').never(); + + assert.isNull(exp.add()); + }); }); describe('addAll()', () => { - it('Should add new elements', () => {}); - it('Should NOT add new elements', () => {}); + it('Should add new elements', () => { + const toAdd = [4,5]; + const exp = Collection.new([1,2,3]); + const expEmit = this.mockCollection.expects('emit') + .once() + .withArgs(Collection.events.addall, exp, toAdd) + .returns(exp); + + assert.instanceOf(exp.addAll(toAdd), Collection); + assert.isFalse(exp.isEmpty()); + assert.equal(5, exp.size()); + assert.equal(4, exp.get(3)); + }); + + it('Should NOT add new elements', () => { + const exp = Collection.new(); + const expEmit = this.mockCollection.expects('emit').never(); + + assert.instanceOf(exp.addAll(), Collection); + assert.instanceOf(exp.addAll({ invalid: true }), Collection); + }); }); describe('get()', () => { - it('Should get an element', () => {}); - it('Should NOT get an element', () => {}); + it('Should get an element', () => { + const exp = Collection.new(['hello', 'world']); + assert.equal('world', exp.get(1)); + assert.equal('hello', exp.get()); + }); + + it('Should NOT get an element', () => { + const exp = Collection.new(['hello', 'world']); + assert.isUndefined(exp.get(3)); + }); }); describe('contains()', () => { - it('Should contain an element', () => {}); - it('Should NOT contain an element', () => {}); + it('Should contain an element (without interface)', () => { + const exp = Collection.new([{ option: 1 }, { option: 2 }]); + assert.isTrue(exp.contains({ option: 2 })); + }); + + it('Should contain an element (with interface)', () => { + const exp = Collection.new([{ env: 'production' }, { env: 'staging' }], { interface: Command }); + assert.isTrue(exp.contains(exp.get(1).toJSON())); + }); + + it('Should NOT contain an element', () => { + const exp = Collection.new([{ option: 1 }, { option: 2 }]); + assert.isFalse(exp.contains({ option: 3 })); + assert.isFalse(exp.contains()); + }); }); describe('containsAl()', () => { - it('Should contain all elements', () => {}); - it('Should NOT contain at least one element', () => {}); + xit('Should contain all elements', () => {}); + xit('Should NOT contain at least one element', () => {}); }); describe('containsWhere()', () => { - it('Should contain an element with condition', () => {}); - it('Should NOT contain an element with condition', () => {}); + xit('Should contain an element with condition', () => {}); + xit('Should NOT contain an element with condition', () => {}); }); describe('remove()', () => { - it('Should remove an element', () => {}); - it('Should NOT remove an element', () => {}); + xit('Should remove an element', () => {}); + xit('Should NOT remove an element', () => {}); }); describe('removeAll()', () => { - it('Should remove all the elements', () => {}); - it('Should NOT remove all the elements', () => {}); + xit('Should remove all the elements', () => {}); + xit('Should NOT remove all the elements', () => {}); }); describe('removeBy()', () => { - it('Should remove elments by predicate', () => {}); - it('Should NOT remove elements by predicate', () => {}); + xit('Should remove elments by predicate', () => {}); + xit('Should NOT remove elements by predicate', () => {}); }); describe('sort()', () => { - it('Should sort by comparator', () => {}); - it('Should NOT sort', () => {}); + xit('Should sort by comparator', () => {}); + xit('Should NOT sort', () => {}); }); describe('iterator()', () => { - it('Should get an iterator from collection', () => {}); + xit('Should get an iterator from collection', () => {}); }); describe('reset()', () => { - it('Should reset the collection', () => {}); + it('Should reset the collection', () => { + const exp = Collection.new(); + const expEmit = this.mockCollection.expects('emit') + .once() + .withArgs(Collection.events.reset, exp) + .returns(exp); + + exp.addAll([1,2], { silent: true }); + assert.equal(2, exp.size()); + + exp.reset(); + assert.equal(0, exp.size()); + }); }); describe('size()', () => { - it('Should get the size of the collection', () => {}); + it('Should get the size of the collection', () => { + assert.equal(0, Collection.new().size()); + assert.equal(2, Collection.new([1,2]).size()); + }); }); describe('isEmpty()', () => { - it('Should be empty', () => {}); - it('Should NOT be empty', () => {}); + it('Should be empty', () => { + assert.isTrue(Collection.new().isEmpty()); + }); + + it('Should NOT be empty', () => { + assert.isFalse(Collection.new([1]).isEmpty()); + }); }); describe('hasInterface()', () => { - it('Should have interface defined', () => {}); - it('Should NOT have interface defined', () => {}); + it('Should have interface defined', () => { + const exp = Collection.new([{ env: 'staging' }, { env: 'production' }], { interface: Command }); + assert.isTrue(exp.hasInterface()); + }); + + it('Should NOT have interface defined', () => { + const exp = Collection.new([]); + assert.isFalse(exp.hasInterface()); + }); }); describe('toJSON()', () => { - it('Should get a json representation', () => {}); + it('Should get a json representation (without interface)', () => { + const exp = Collection.new([1,2,'value']).toJSON(); + assert.isArray(exp); + assert.equal(3, exp.length); + assert.equal('value', exp[2]); + }); + + it('Should get a json representation (with interface)', () => { + const exp = Collection.new([{ env: 'staging' }, { env: 'production' }], { interface: Command }).toJSON(); + assert.isArray(exp); + assert.equal(2, exp.length); + assert.equal('production', exp[1].env); + }); }); From 0e31b9e6f2372fb364984dd386adf078fdbb1f40 Mon Sep 17 00:00:00 2001 From: kuakman <3dimentionar@gmail.com> Date: Thu, 2 Mar 2017 11:55:18 -0800 Subject: [PATCH 06/39] util: checkpoint - Completed Unit Test coverage on commands.util.adt.Collection and fixed commands.util.adt.Stack test cases. --- src/commands/util/adt/collection.es6 | 106 +++++--- src/commands/util/adt/stack.es6 | 4 +- test/commands/util/adt/collection.spec.es6 | 280 +++++++++++++++++++-- test/commands/util/adt/stack.spec.es6 | 17 +- 4 files changed, 342 insertions(+), 65 deletions(-) diff --git a/src/commands/util/adt/collection.es6 b/src/commands/util/adt/collection.es6 index 8b85139..10f3902 100644 --- a/src/commands/util/adt/collection.es6 +++ b/src/commands/util/adt/collection.es6 @@ -11,7 +11,7 @@ import Iterator from './iterator'; * Class Collection * @extends events.EventEmitter **/ -export default class Collection extends EventEmitter { +class Collection extends EventEmitter { /** * Internal Array @@ -29,9 +29,6 @@ export default class Collection extends EventEmitter { /** * Constructor - * @FIXME: Reminder: Important fix the underscore aggregation: - * Not do it per instance (it adds overhead), - * instead apply it to the prototype of this class! * @public * @param [initial = []] {Array} Initial Array * @param [opts = {}] {Object} collection options @@ -39,7 +36,7 @@ export default class Collection extends EventEmitter { **/ constructor(initial = [], opts = {}) { super(); - Collection._aggregate(extend(true, this, { _interface: opts.interface })) + extend(true, this, { _interface: opts.interface }); return (initial.length > 0) ? this.set(initial) : this; } @@ -62,7 +59,7 @@ export default class Collection extends EventEmitter { * @param [...args] {Any} additional arguments to pass to the emit * @return {commands.util.adt.Collection} **/ - _fire(name, opts = {}, ...args) { + _fire(name, opts, ...args) { return !opts.silent ? this.emit(name, this, ...args) : this; } @@ -79,15 +76,25 @@ export default class Collection extends EventEmitter { return (this.hasInterface() && !_.instanceOf(e, this._interface)) ? w() : wo(); } + /** + * Returns a json representation of a given element only if this collection has defined an interface + * and the given element implements {commands.util.proxy.Json} interface + * @private + * @param element {Any} element to get json representation + * @return {Any} + **/ + _toJSON(element) { + return (this.hasInterface() && element.toJSON) ? element.toJSON() : element; + } + /** * Default instanciation strategy for new elements added in this collection * @private * @param e {Any} element to instanciate - * @param [opts = {}] {Object} additional options + * @param opts {Object} additional options * @return {Any} **/ - _new(e, opts = {}) { - if(!this._valid(e)) return null; + _new(e, opts) { return this._when(e, () => _.defined(opts.new) ? opts.new(e) : new this._interface(e), () => e); } @@ -116,9 +123,10 @@ export default class Collection extends EventEmitter { **/ add(element, opts = {}) { if(!this._valid(element)) return null; - this._collection.push(this._new(element, opts)); - this._fire(Collection.events.add, opts, element); - return element; + let added = this._new(element, opts); + this._collection.push(added); + this._fire(Collection.events.add, opts, added); + return added; } /** @@ -153,7 +161,7 @@ export default class Collection extends EventEmitter { **/ contains(element) { if(!this._valid(element)) return false; - return this.some((e) => _.isEqual((this.hasInterface() && e.toJSON) ? e.toJSON() : e, element)); + return this.some((e) => _.isEqual(this._toJSON(e), element)); } /** @@ -163,7 +171,7 @@ export default class Collection extends EventEmitter { * @return {Boolean} **/ containsAll(elements = []) { - if(!this._valid(elements)) return false; + if(!this._valid(elements) || elements.length === 0) return false; return _.every(_.map(elements, this.contains, this)); } @@ -179,18 +187,34 @@ export default class Collection extends EventEmitter { } /** - * Removes an element at the given index. If no index is passed, this method will remove the last element in + * Removes an element at the given index. If no index is passed, this method will remove the first element in * this collection. * @public * @fires {Collection.events.remove} - * @param [ix = (this._collection.size() - 1)] {Number} index used to remove + * @param [ix = 0] {Number} index used to remove * @param [opts = {}] {Object} additional options - * @return {commands.util.adt.Collection} + * @return {Number} + **/ + removeAt(ix = 0, opts = {}) { + if(!this._valid(ix) || !_.isNumber(ix) || ix > (this.size() - 1)) return null; + this._fire(Collection.events.remove, opts, this._collection.splice(ix, 1)); + return ix; + } + + /** + * Remove a given element + * @public + * @fires {Collection.events.remove} + * @param element {Any} element to remove + * @param [opts = {}] {Object} additional options + * @return {Number} **/ - remove(ix = (this._collection.size() - 1), opts = {}) { - if(!this._valid(ix) || !_.isNumber(ix) || ix > (this.size() - 1)) return this; - this._collection.splice(ix, 1); - return this._fire(Collection.events.remove, opts); + remove(element, opts = {}) { + if(!this._valid(element)) return null; + let ix = this.findIndex((e) => _.isEqual(this._toJSON(e), this._toJSON(element))); + if(ix === -1) return null; + this._fire(Collection.events.remove, opts, this._collection.splice(ix, 1)); + return element; } /** @@ -202,18 +226,26 @@ export default class Collection extends EventEmitter { * @return {commands.util.adt.Collection} **/ removeAll(col = [], opts = {}) { - - return this._fire(Collection.events.removeall, opts); + if(!_.isArray(col) || col.length === 0) return this; + let removed = _.map(col, (e) => this.remove(e, extend(true, {}, opts, { silent: true }))); + return this._fire(Collection.events.removeall, opts, _.compact(removed)); } /** * Removes elements by a given predicate * @public + * @fires {Collection.events.remove} * @param predicate {Function} predicate used to evaluate + * @param [opts = {}] {Object} additional options * @return {commands.util.adt.Collection} **/ - removeBy() { - // TODO + removeBy(predicate, opts = {}) { + if(!this._valid(predicate) || !_.isFunction(predicate)) return this; + for(var i = 0, len = this.size(); i < len; i++) { + if(predicate(this._collection[i], i, this._collection)) { + this.removeAt(i, opts); i--; len--; + } + } return this; } @@ -227,7 +259,9 @@ export default class Collection extends EventEmitter { * @return {commands.util.adt.Collection} **/ sort(comparator, opts = {}) { - // TODO + (!_.defined(comparator) || !_.isFunction(comparator)) ? + this._collection.sort() : + this._collection.sort(comparator); return this._fire(Collection.events.sort, opts); } @@ -333,9 +367,9 @@ export default class Collection extends EventEmitter { /** * Underscore interface methods for aggregation * @static - * @type Array + * @return {Array} **/ - static UNDERSCORE = [ + static UNDERSCORE = () => [ 'each', 'map', 'findWhere', @@ -377,14 +411,14 @@ export default class Collection extends EventEmitter { * @param instance {commands.util.adt.Collection} * @return {commands.util.adt.Collection} **/ - static _aggregate(instance) { - _.each(this.UNDERSCORE, function(method) { - if(_[method] && !_.defined(instance[method])) { - instance[method] = _.bind(function() { - return _[method].apply(this, [this._collection].concat(_.toArray(arguments))); - }, instance); - } + static _aggregate() { + _.each(this.UNDERSCORE(), function(method) { + if(!_[method] || _.defined(this.prototype[method])) return; + this.prototype[method] = function() { + return _[method].apply(this, [this._collection].concat(_.toArray(arguments))); + }; }, this); + return this; } /** @@ -398,3 +432,5 @@ export default class Collection extends EventEmitter { } } + +export default Collection._aggregate(); diff --git a/src/commands/util/adt/stack.es6 b/src/commands/util/adt/stack.es6 index d10b180..2d65c5b 100644 --- a/src/commands/util/adt/stack.es6 +++ b/src/commands/util/adt/stack.es6 @@ -66,7 +66,7 @@ class Stack extends Collection { **/ pop() { if(this.size() <= 0) return null; - let removed = this.remove(0, { silent: true }); + let removed = this.removeAt(0, { silent: true }); this._fire(Stack.events.pop, {}, removed); return removed; } @@ -79,7 +79,7 @@ class Stack extends Collection { **/ search(element) { if(!this._valid(element)) return -1; - return this.findIndex((e) => _.isEqual(((this.hasInterface() && _.defined(e.toJSON)) ? e.toJSON() : e), element)); + return this.findIndex((e) => _.isEqual(this._toJSON(e), this._toJSON(element))); } /** diff --git a/test/commands/util/adt/collection.spec.es6 b/test/commands/util/adt/collection.spec.es6 index 766d659..d5ef2a3 100644 --- a/test/commands/util/adt/collection.spec.es6 +++ b/test/commands/util/adt/collection.spec.es6 @@ -4,6 +4,7 @@ **/ import Collection from 'commands/util/adt/collection'; import Command from 'commands/command'; +import Iterator from 'commands/util/adt/iterator'; describe('commands.util.adt.Collection', function() { @@ -12,12 +13,17 @@ describe('commands.util.adt.Collection', function() { }); beforeEach(() => { + this.mockStatic = this.sandbox.mock(Collection); this.mockCollection = this.sandbox.mock(Collection.prototype); }); afterEach(() => { + this.mockStatic.verify(); this.mockCollection.verify(); + this.sandbox.restore(); + + delete this.mockStatic; delete this.mockCollection; }); @@ -98,8 +104,8 @@ describe('commands.util.adt.Collection', function() { const exp = Collection.new([], { interface: Command }); const expEmit = this.mockCollection.expects('emit') .once() - .withArgs(Collection.events.add, exp, toAdd) - .returns(toAdd); + .withArgs(Collection.events.add, exp, sinon.match.instanceOf(Command)) + .returns(sinon.match.instanceOf(Command)); exp.add(toAdd); assert.isFalse(exp.isEmpty()); @@ -116,6 +122,20 @@ describe('commands.util.adt.Collection', function() { assert.instanceOf(exp.get(0), Command); }); + it('Should add a new element (with custom new instanciation)', () => { + const toAdd = { env: 'production' }; + const _new = (attrs) => new Command({ env: 'prod' }); + const spyNew = this.sandbox.spy(_new); + const exp = Collection.new([], { interface: Command }); + const expEmit = this.mockCollection.expects('emit') + .once() + .withArgs(Collection.events.add, exp, sinon.match.instanceOf(Command)) + .returns(sinon.match.instanceOf(Command)); + + assert.instanceOf(exp.add(toAdd, { new: spyNew }), Command); + assert.equal(true, spyNew.calledOnce); + }); + it('Should NOT add a new element', () => { const exp = Collection.new(); const expEmit = this.mockCollection.expects('emit').never(); @@ -186,51 +206,259 @@ describe('commands.util.adt.Collection', function() { }); - describe('containsAl()', () => { + describe('containsAll()', () => { - xit('Should contain all elements', () => {}); - xit('Should NOT contain at least one element', () => {}); + it('Should contain all elements', () => { + const exp = Collection.new([{ option: 1 }, { option: 2 }, { option: 3 }]); + + assert.isTrue(exp.containsAll([{ option: 1 }, { option: 3 }])); + assert.isFalse(exp.containsAll([{ option: 1 }, { option: 2 }, { option: 3 }, { option: 4 }])); + }); + + it('Should NOT contain at least one element', () => { + const exp = Collection.new([{ option: 1 }, { option: 2 }, { option: 3 }]); + + assert.isFalse(exp.containsAll([{ option: 1 }, { value: 2 }])); + assert.isFalse(exp.containsAll([])); + assert.isFalse(exp.containsAll()); + }); }); describe('containsWhere()', () => { - xit('Should contain an element with condition', () => {}); - xit('Should NOT contain an element with condition', () => {}); + it('Should contain an element with condition', () => { + const exp = Collection.new([{ prop: 'A' }, { prop: 'B' }]); + assert.isTrue(exp.containsWhere({ prop: 'B' })); + assert.isTrue(exp.containsWhere()); + }); + + it('Should NOT contain an element with condition', () => { + const exp = Collection.new([{ prop: 'A' }, { prop: 'B' }]); + assert.isFalse(exp.containsWhere({ prop: 'C' })); + }); + + }); + + describe('removeAt()', () => { + + it('Should remove an element at index (without interface)', () => { + const toRemove = 2; + const exp = Collection.new([1,2,3]); + const expEmit = this.mockCollection.expects('emit') + .once() + .withArgs(Collection.events.remove, exp, [toRemove]) + .returns(exp); + + assert.equal(1, exp.removeAt(1)); + assert.equal(2, exp.size()); + assert.equal(3, exp.get(1)); + }); + + it('Should remove an element at index (with interface)', () => { + const exp = Collection.new([{ env: 'staging' }, { env: 'production' }], { interface: Command }); + const toRemove = exp.get(1); + const expEmit = this.mockCollection.expects('emit') + .once() + .withArgs(Collection.events.remove, exp, [toRemove]) + .returns(exp); + + assert.equal(1, exp.removeAt(1)); + assert.equal(1, exp.size()); + assert.notEqual(toRemove.toJSON(), exp.get(0).toJSON()); + }); + + it('Should NOT remove an element at index (default index)', () => { + const exp = Collection.new(); + const expEmit = this.mockCollection.expects('emit').never(); + assert.isNull(exp.removeAt()); + }); + + it('Should NOT remove an element at index (index not a number)', () => { + const exp = Collection.new(); + const expEmit = this.mockCollection.expects('emit').never(); + assert.isNull(exp.removeAt('hello')); + }); + + it('Should NOT remove an element at index (index is greater that size - 1)', () => { + const exp = Collection.new([1,2]); + const expEmit = this.mockCollection.expects('emit').never(); + assert.isNull(exp.removeAt(2)); + }); }); describe('remove()', () => { - xit('Should remove an element', () => {}); - xit('Should NOT remove an element', () => {}); + it('Should remove an element (without interface)', () => { + const toRemove = 2; + const exp = Collection.new([1,2,3]); + const expEmit = this.mockCollection.expects('emit') + .once() + .withArgs(Collection.events.remove, exp, [toRemove]) + .returns(exp); + + assert.equal(toRemove, exp.remove(toRemove)); + assert.equal(2, exp.size()); + assert.equal(3, exp.get(1)); + }); + + it('Should remove an element (with interface)', () => { + const exp = Collection.new([{ env: 'staging' }, { env: 'production' }], { interface: Command }); + const toRemove = exp.get(1); + const expEmit = this.mockCollection.expects('emit') + .once() + .withArgs(Collection.events.remove, exp, [toRemove]) + .returns(exp); + + assert.equal(toRemove, exp.remove(toRemove)); + assert.equal(1, exp.size()); + assert.notEqual(toRemove, exp.get(0)); + }); + + it('Should NOT remove an element (element is invalid)', () => { + const exp = Collection.new([1,2]); + const expEmit = this.mockCollection.expects('emit').never(); + assert.isNull(exp.remove()); + }); + }); describe('removeAll()', () => { - xit('Should remove all the elements', () => {}); - xit('Should NOT remove all the elements', () => {}); + it('Should remove all the elements', () => { + const toRemove = [1,2]; + const exp = Collection.new([1,2,3]); + const expEmit = this.mockCollection.expects('emit') + .once() + .withArgs(Collection.events.removeall, exp, toRemove) + .returns(exp); + + assert.instanceOf(exp.removeAll(toRemove), Collection); + assert.isFalse(exp.isEmpty()); + assert.equal(1, exp.size()); + assert.equal(3, exp.get(0)); + }); + + it('Should remove a few elements (matching)', () => { + const toRemove = [1,5,2]; + const exp = Collection.new([1,2,3]); + const expEmit = this.mockCollection.expects('emit') + .once() + .withArgs(Collection.events.removeall, exp, [1,2]) + .returns(exp); + + assert.instanceOf(exp.removeAll(toRemove), Collection); + assert.isFalse(exp.isEmpty()); + assert.equal(1, exp.size()); + assert.equal(3, exp.get(0)); + }); + + it('Should NOT remove all the elements', () => { + const toRemove = [4,5]; + const exp = Collection.new([1,2,3]); + const expEmit = this.mockCollection.expects('emit') + .once() + .withArgs(Collection.events.removeall, exp, []) + .returns(exp); + + assert.instanceOf(exp.removeAll(toRemove), Collection); + assert.isFalse(exp.isEmpty()); + assert.equal(3, exp.size()); + assert.equal(1, exp.get(0)); + }); + + it('Should NOT remove all the elements (elements not an array)', () => { + const toRemove = { invalid: 1 }; + const exp = Collection.new([1,2,3]); + const expEmit = this.mockCollection.expects('emit').never(); + + assert.instanceOf(exp.removeAll(toRemove), Collection); + assert.instanceOf(exp.removeAll(), Collection); + + assert.isFalse(exp.isEmpty()); + assert.equal(3, exp.size()); + }); }); describe('removeBy()', () => { - xit('Should remove elments by predicate', () => {}); - xit('Should NOT remove elements by predicate', () => {}); + it('Should remove elments by predicate', () => { + const predicate = (e) => (e.env === 'stage' || e.env === 'dev'); + let spyPredicate = this.sandbox.spy(predicate); + const exp = Collection.new([{ env: 'stage' }, { env: 'dev' }, { env: 'prod' }], { interface: Command }); + const expEmit = this.mockCollection.expects('emit') + .exactly(2) + .withArgs(Collection.events.remove, exp, [sinon.match.instanceOf(Command)]) + .returns(sinon.match.instanceOf(Command)); + + assert.instanceOf(exp.removeBy(spyPredicate), Collection); + assert.equal(1, exp.size()); + assert.equal('prod', exp.get(0).env); + assert.equal(true, spyPredicate.calledThrice); + }); + + it('Should NOT remove elements by predicate', () => { + const predicate = (v) => v === 3; + let spyPredicate = this.sandbox.spy(predicate); + const exp = Collection.new([1,2]); + const expEmit = this.mockCollection.expects('emit').never(); + + assert.instanceOf(exp.removeBy(spyPredicate), Collection); + assert.equal(2, exp.size()); + assert.equal(true, spyPredicate.calledTwice); + }); + + it('Should NOT remove elements by predicate (no predicate)', () => { + const exp = Collection.new([1,2]); + const expEmit = this.mockCollection.expects('emit').never(); + + assert.instanceOf(exp.removeBy(), Collection); + assert.equal(2, exp.size()); + }); }); describe('sort()', () => { - xit('Should sort by comparator', () => {}); - xit('Should NOT sort', () => {}); + it('Should sort by comparator', () => { + const exp = Collection.new([{ v: 3 }, { v: 1 }, { v: 2 }]); + const expEmit = this.mockCollection.expects('emit') + .once() + .withArgs(Collection.events.sort, exp) + .returns(exp); + + exp.sort((a, b) => (a.v - b.v)); + + assert.equal(1, exp.get(0).v); + assert.equal(2, exp.get(1).v); + assert.equal(3, exp.get(2).v); + }); + + it('Should sort using default', () => { + const exp = Collection.new(['Hello', '1 Hello', '2 World', 'World']); + const expEmit = this.mockCollection.expects('emit') + .once() + .withArgs(Collection.events.sort, exp) + .returns(exp); + + exp.sort(); + + assert.equal('1 Hello', exp.get(0)); + assert.equal('2 World', exp.get(1)); + assert.equal('Hello', exp.get(2)); + assert.equal('World', exp.get(3)); + }); }); describe('iterator()', () => { - xit('Should get an iterator from collection', () => {}); + it('Should get an iterator from collection', () => { + assert.instanceOf(Collection.new([1,2,3]).iterator(), Iterator); + }); }); @@ -305,4 +533,24 @@ describe('commands.util.adt.Collection', function() { }); + describe('static->_aggregate()', () => { + + it('Should NOT aggregate underscore methods (already implemented)', () => { + const expUNDERSCORE = this.mockStatic.expects('UNDERSCORE') + .once() + .returns(['remove']); + + assert.typeOf(Collection._aggregate(), 'Function'); + }); + + it('Should NOT aggregate underscore methods (not as part of underscore)', () => { + const expUNDERSCORE = this.mockStatic.expects('UNDERSCORE') + .once() + .returns(['unexistent']); + + assert.typeOf(Collection._aggregate(), 'Function'); + }); + + }); + }); diff --git a/test/commands/util/adt/stack.spec.es6 b/test/commands/util/adt/stack.spec.es6 index 02d1987..b3ef0f1 100644 --- a/test/commands/util/adt/stack.spec.es6 +++ b/test/commands/util/adt/stack.spec.es6 @@ -16,6 +16,7 @@ describe('commands.util.adt.Stack', function() { }); afterEach(() => { + this.mockStack.verify(); this.sandbox.restore(); delete this.mockStack; }); @@ -47,25 +48,19 @@ describe('commands.util.adt.Stack', function() { const exp = Stack.new([], { interface: Command }); const expEmit = this.mockStack.expects('emit') .once() - .withArgs(Stack.events.push, exp, toPush) + .withArgs(Stack.events.push, exp, sinon.match.instanceOf(Command)) .returns(exp); - exp.push(toPush); - + assert.isTrue(exp.push(toPush)); assert.isFalse(exp.isEmpty()); - - this.mockStack.verify(); }); it('Should NOT push a new element', () => { const exp = Stack.new(); const expEmit = this.mockStack.expects('emit').never(); - exp.push(); - + assert.isFalse(exp.push()); assert.isTrue(exp.isEmpty()); - - this.mockStack.verify(); }); }); @@ -94,11 +89,9 @@ describe('commands.util.adt.Stack', function() { .withArgs(Stack.events.pop, exp) .returns(exp); - assert.instanceOf(exp.pop(), Stack); + assert.equal(0, exp.pop()); assert.instanceOf(exp.peek(), Command); assert.equal(1, exp.size()); - - this.mockStack.verify(); }); it('Should NOT remove and get the first element', () => { From 591d2aa46a6fa4a811697ac9fbb507d72ada729f Mon Sep 17 00:00:00 2001 From: kuakman <3dimentionar@gmail.com> Date: Thu, 2 Mar 2017 17:53:45 -0800 Subject: [PATCH 07/39] util: checkpoint - Added Unit Test Coverage on commands.util.adt.Queue. Fixed Major issue with proto members declared in classes. --- src/commands/command.es6 | 11 +- src/commands/util/adt/collection.es6 | 19 +-- src/commands/util/adt/queue.es6 | 78 ++++----- src/commands/util/adt/stack.es6 | 2 +- src/commands/util/exception/adt/queue.es6 | 13 +- .../util/exception/command/command.es6 | 9 +- src/commands/util/exception/exception.es6 | 33 ++-- test/commands/util/adt/queue.spec.es6 | 158 ++++++++++++++++++ test/global.js | 1 + 9 files changed, 236 insertions(+), 88 deletions(-) diff --git a/src/commands/command.es6 b/src/commands/command.es6 index 2e7a94f..8da7f77 100644 --- a/src/commands/command.es6 +++ b/src/commands/command.es6 @@ -19,13 +19,6 @@ import CommandException from './util/exception/command/command'; **/ export default class Command extends EventEmitter { - /** - * Commands Collection - * @private - * @type {commands.util.adt.Collection} - **/ - _commands = new Collection([], { interface: Command }) - /** * Constructor * @public @@ -34,7 +27,9 @@ export default class Command extends EventEmitter { **/ constructor(args) { super(); - return JSON.proxy(extend(true, this, this.defaults, _.pick(args, this.constructor.options))); + return JSON.proxy(extend(true, this, this.defaults, _.pick(args, this.constructor.options), { + _commands: new Collection([], { interface: Command }) + })); } /** diff --git a/src/commands/util/adt/collection.es6 b/src/commands/util/adt/collection.es6 index 10f3902..d350f3a 100644 --- a/src/commands/util/adt/collection.es6 +++ b/src/commands/util/adt/collection.es6 @@ -13,20 +13,6 @@ import Iterator from './iterator'; **/ class Collection extends EventEmitter { - /** - * Internal Array - * @private - * @type {Array} - **/ - _collection = []; - - /** - * Interface for objects - * @private - * @type {Function} - **/ - _interface = null; - /** * Constructor * @public @@ -36,7 +22,7 @@ class Collection extends EventEmitter { **/ constructor(initial = [], opts = {}) { super(); - extend(true, this, { _interface: opts.interface }); + extend(true, this, _.omit(opts, 'silent', 'interface'), { _collection: [], _interface: opts.interface }); return (initial.length > 0) ? this.set(initial) : this; } @@ -60,7 +46,8 @@ class Collection extends EventEmitter { * @return {commands.util.adt.Collection} **/ _fire(name, opts, ...args) { - return !opts.silent ? this.emit(name, this, ...args) : this; + if(!opts.silent) this.emit(name, this, ...args); + return this; } /** diff --git a/src/commands/util/adt/queue.es6 b/src/commands/util/adt/queue.es6 index 89fccca..e17632a 100644 --- a/src/commands/util/adt/queue.es6 +++ b/src/commands/util/adt/queue.es6 @@ -15,7 +15,7 @@ import QueueException from 'commands/util/exception/adt/queue'; * * let myqueue = new Queue([1,2,3], { capacity: 4 }); // initial is set to capacity was set to 4 * myqueue.offer(4); // Adds one more element without violating capacity (3) -* myqueue.poll(); +* myqueue.peek(); * * let myqueue = new Queue([{ name: 'one' }, { name: 'two' }], { capacity: 3, interface: MyClass }); * myqueue.offer({ name: 3 }); // Adds one more element without violating capacity (3) @@ -24,46 +24,42 @@ import QueueException from 'commands/util/exception/adt/queue'; **/ export default class Queue extends Collection { - /** - * Queue capacity - * @public - * @type {Number} - **/ - capacity = 0 - /** * Constructor * @public - * @override - * @param [intial = []] {Array} initial collection of elements - * @param [opts = {}] {Object} additional options - * @return {commands.util.adt.Queue} + * @param [initial = []] {Array} Initial Array + * @param [opts = {}] {Object} collection options + * @return {commands.util.adt.Collection} **/ constructor(initial = [], opts = {}) { - super(initial, opts); - return extend(true, this, { capacity: opts.capacity }); + const { capacity } = opts; + return super(initial, extend(true, opts, _.defined(capacity) ? { capacity } : { capacity: 0 })); } /** - * Validates capacity of the queue to either decide, to add or not the element on this queue - * @TODO: Keep an eye on the order of validations while unit testing... + * Validates element or array of elements to decide either, to add or not the elements on this queue * @private * @override * @throws {commands.util.exceptions.QueueException} * @param element {Any} element to validate - * @param opts {Object} additional options * @return {Boolean} **/ - _valid(element, opts) { - if(this.size() >= this.capacity) return false; - if(!_.defined(opts.capacity)) - throw QueueException.new({ type: 'capacityUndefined', level: QueueException.fatal }); - if(_.isArray(element) && element.length > this.capacity) - throw QueueException.new({ type: 'capacityViolation', level: QueueException.fatal }, - { capacity: this.capacity }); + _valid(element) { + const { capacity } = this; + if(_.isArray(element) && (element.length > capacity)) + throw QueueException.new('capacityViolation', { level: QueueException.fatal, capacity }); return super._valid(element); } + /** + * Returns true if the current size has reached the capacity of the queye, false otherwise + * @private + * @return {Boolean} + **/ + _validCapacity() { + return (this.size() < this.capacity); + } + /** * Set initial collection of elements on this queue * @public @@ -73,9 +69,8 @@ export default class Queue extends Collection { * @return {commands.util.adt.Queue} **/ set(col = [], opts = {}) { - if(!this._valid(col, opts)) return this; - this.capacity = opts.capacity; - return super.set(col); + if(!this._validCapacity() || !this._valid(col) || !_.isArray(col) || col.length === 0) return this; + return super.set(col, opts); } /** @@ -88,9 +83,8 @@ export default class Queue extends Collection { * @return {Boolean} **/ offer(element, opts = {}) { - if(!this._valid(element, opts)) return false; - this.add(element, extend(true, opts, { silent true })) - ._fire(Queue.events.offer, this, element); + if(!this._validCapacity() || !this._valid(element)) return false; + this._fire(Queue.events.offer, opts, this.add(element, extend(true, {}, opts, { silent: true }))); return true; } @@ -106,10 +100,16 @@ export default class Queue extends Collection { /** * Retrieves and removes the head of this queue, or returns null if this queue is empty * @public + * @param [opts = {}] {Object} additional options * @return {Object} **/ - poll() { - return (this.size() > 0) ? this.remove(0, { silent: true })._fire(Queue.events.poll) : null; + poll(opts = {}) { + if(this.size() > 0) { + let polled = this.remove(this.get(0), { silent: true }); + this._fire(Queue.events.poll, opts, polled); + return polled; + } + return null; } /** @@ -117,7 +117,7 @@ export default class Queue extends Collection { * @static * @type {Object} **/ - static events = extend(false, Collection.events, { + static events = extend(false, {}, Collection.events, { /** * @event offer **/ @@ -127,16 +127,6 @@ export default class Queue extends Collection { * @event poll **/ poll: 'commands:util:adt:queue:poll' - }) - - /** - * Static constructor - * @static - * @param [...args] {Any} Constructor arguments - * @return {commands.util.adt.Queue} - **/ - static new(...args) { - return new this(...args); - } + }); } diff --git a/src/commands/util/adt/stack.es6 b/src/commands/util/adt/stack.es6 index 2d65c5b..57ec2c2 100644 --- a/src/commands/util/adt/stack.es6 +++ b/src/commands/util/adt/stack.es6 @@ -87,7 +87,7 @@ class Stack extends Collection { * @static * @type {Object} **/ - static events = extend(false, Collection.events, { + static events = extend(false, {}, Collection.events, { /** * @event push **/ diff --git a/src/commands/util/exception/adt/queue.es6 b/src/commands/util/exception/adt/queue.es6 index 89a2536..68a589f 100644 --- a/src/commands/util/exception/adt/queue.es6 +++ b/src/commands/util/exception/adt/queue.es6 @@ -12,15 +12,20 @@ import Exception from '../exception'; **/ export default class QueueException extends Exception { + /** + * Exception Name + * @public + * @type {String} + **/ + name = 'QueueException'; + /** * Command Exception types * @public * @type {Object} **/ - static type = extend(true, Exception.type, { - capacityUndefined: _.template(`Queue requires a 'capacity' property in order to be instanciate it`), - capacityViolation: _.template(`Queue element's collection passed overflows the current capacity - <%= capacity %>`) + static type = extend(true, {}, Exception.type, { + capacityViolation: _.template(`Queue element's collection passed overflows the current capacity <%= capacity %>`) }); } diff --git a/src/commands/util/exception/command/command.es6 b/src/commands/util/exception/command/command.es6 index f51be4e..eac851c 100644 --- a/src/commands/util/exception/command/command.es6 +++ b/src/commands/util/exception/command/command.es6 @@ -12,12 +12,19 @@ import Exception from '../exception'; **/ export default class CommandException extends Exception { + /** + * Exception Name + * @public + * @type {String} + **/ + name = 'CommandException'; + /** * Command Exception types * @public * @type {Object} **/ - static type = extend(true, Exception.type, { + static type = extend(true, {}, Exception.type, { chain: _.template('Required parameter `command` to be an instance of `commands.Command`') }); diff --git a/src/commands/util/exception/exception.es6 b/src/commands/util/exception/exception.es6 index e9ec61b..d4fa6a6 100644 --- a/src/commands/util/exception/exception.es6 +++ b/src/commands/util/exception/exception.es6 @@ -13,22 +13,24 @@ import Logger from '../logger/logger'; export default class Exception extends Error { /** - * Constructor + * Exception Name * @public - * @param [args = { type: 'unknown' }] {Object} constructor attribute + * @type {String} **/ - constructor(args = { type: 'unknown' }) { - super({ message: Command.type[args.type] }); - return extend(true, this, _.pick(args, 'level')); - } + name = 'Exception'; /** - * Exception Message + * Constructor * @public - * @type {String} + * @override + * @param message {Function} message template function + * @param [...args] {Any} constructor attribute + * @return {commands.util.exception.Exception} **/ - get message() { - // TODO + constructor(message, ...args) { + super(message); + Error.captureStackTrace(this, this.constructor); + return this; } /** @@ -38,14 +40,17 @@ export default class Exception extends Error { **/ static type = { unknown: _.template('Unknown Exception') - } + }; /** + * Static Constructor * @static - * @param [...args] {Any} + * @param [type = 'unknown'] + * @param [...args] {Any} additional arguments + * @return {commands.util.exception.Exception} **/ - static new(...args) { - return new this(...args); + static new(type, ...args) { + return new this(this.type[type](...args), ...args); } } diff --git a/test/commands/util/adt/queue.spec.es6 b/test/commands/util/adt/queue.spec.es6 index e69de29..f18d0f3 100644 --- a/test/commands/util/adt/queue.spec.es6 +++ b/test/commands/util/adt/queue.spec.es6 @@ -0,0 +1,158 @@ +/** +* @module commands.util.adt +* @author Patricio Ferreira <3dimentionar@gmail.com> +**/ +import Queue from 'commands/util/adt/queue'; +import Command from 'commands/command'; +import QueueException from 'commands/util/exception/adt/queue'; + +describe('commands.util.adt.Queue', function() { + + before(() => { + this.sandbox = sinon.sandbox.create(); + }); + + beforeEach(() => { + this.mockStatic = this.sandbox.mock(Queue); + this.mockQueue = this.sandbox.mock(Queue.prototype); + }); + + afterEach(() => { + this.mockStatic.verify(); + this.mockQueue.verify(); + + this.sandbox.restore(); + + delete this.mockStatic; + delete this.mockQueue; + }); + + after(() => { + delete this.sandbox; + }); + + describe('constructor()', () => { + + it('Should get a new instance', () => { + assert.instanceOf(Queue.new(), Queue); + }); + + }); + + describe('_valid()', () => { + + it('Should ERROR: elements is array and length is >= than capacity', () => { + const exp = Queue.new([], { capacity: 2 }); + assert.throws(() => exp._valid([1,2,3]), QueueException.type.capacityViolation({ capacity: 2 })); + }); + + it('Should NOT be valid - element is null', () => { + const exp = Queue.new([], { capacity: 2 }); + assert.isFalse(exp._valid()); + }); + + }); + + describe('_validCapacity()', () => { + + it('Should validate capacity to true - current size is <= than capacity', () => { + const exp = Queue.new([], { capacity: 2 }); + const expSize = this.mockQueue.expects('size').once().returns(2); + assert.isFalse(exp._validCapacity()); + }); + + }); + + describe('set()', () => { + + it('Should set elements', () => { + const toSet = [1, 2]; + const exp = Queue.new([], { capacity: 2 }); + const expEmit = this.mockQueue.expects('emit') + .once() + .withArgs(Queue.events.set, exp, toSet) + .returns(exp); + + assert.instanceOf(exp.set(toSet), Queue); + assert.equal(2, exp.size()); + }); + + it('Should NOT set elements (not valid)', () => { + const exp = Queue.new([], { capacity: 2 }); + const expEmit = this.mockQueue.expects('emit').never(); + assert.instanceOf(exp.set(), Queue); + assert.instanceOf(exp.set({}), Queue); + }); + + }); + + describe('offer()', () => { + + it('Should push elements into the queue', () => { + const toOffer = { env: 'stage' }; + const exp = Queue.new([{ env: 'dev' }], { capacity: 3, interface: Command, silent: true }); + const expEmit = this.mockQueue.expects('emit') + .once() + .withArgs(Queue.events.offer, exp, sinon.match.instanceOf(Command)) + .returns(exp); + + assert.isTrue(exp.offer(toOffer)); + assert.equal(2, exp.size()); + }); + + it('Should NOT push elements into the queue', () => { + const toOffer = { env: 'stage' }; + const exp = Queue.new([{ env: 'dev' }], { capacity: 1, interface: Command, silent: true }); + const expEmit = this.mockQueue.expects('emit').never(); + + assert.isFalse(exp.offer(toOffer)); + assert.equal(1, exp.size()); + }); + + }); + + describe('peek()', () => { + + it('Should retrieve, but not remove the next element in the queue', () => { + const exp = Queue.new([{ env: 'dev' }, { env: 'stage' }], { capacity: 2, interface: Command, silent: true }); + let next = exp.peek(); + assert.instanceOf(next, Command); + assert.equal('dev', next.env); + assert.equal(2, exp.size()); + }); + + it('Should NOT retrieve the next element in the queue', () => { + const exp = Queue.new([], { capacity: 2, interface: Command, silent: true }); + let next = exp.peek(); + assert.isNull(next); + assert.isTrue(exp.isEmpty()); + }); + + }); + + describe('poll()', () => { + + it('Should retrieve and remove the next element in the queue', () => { + const exp = Queue.new([{ env: 'dev' }, { env: 'stage' }], { capacity: 3, interface: Command, silent: true }); + const expEmit = this.mockQueue.expects('emit') + .once() + .withArgs(Queue.events.poll, exp, sinon.match.instanceOf(Command)) + .returns(exp); + + let next = exp.poll(); + assert.instanceOf(next, Command); + assert.equal('dev', next.env); + assert.equal(1, exp.size()); + }); + + it('Should NOT retrieve (nor remove) the next element in the queue', () => { + const exp = Queue.new([], { capacity: 2, interface: Command, silent: true }); + const expEmit = this.mockQueue.expects('emit').never(); + + assert.isNull(exp.poll()); + assert.isTrue(exp.isEmpty()); + }); + + }); + +}); diff --git a/test/global.js b/test/global.js index 4e205ff..77da15f 100644 --- a/test/global.js +++ b/test/global.js @@ -4,6 +4,7 @@ **/ global.fs = require('fs-extra'); global.path = require('path'); +global._ = require('underscore'); global.sinon = require('sinon'); global.assert = require('chai').assert; global.basepath = path.join(path.resolve(__dirname, '..'), 'src'); From dfeeb582c5b2bc8363c576a092ce9390fdebf51b Mon Sep 17 00:00:00 2001 From: kuakman <3dimentionar@gmail.com> Date: Thu, 2 Mar 2017 22:21:43 -0800 Subject: [PATCH 08/39] util: checkpoint - added unit test coverage for commands.util.mixins. Fixed prototype member name to be instance member on Exceptions. --- src/commands/util/exception/adt/queue.es6 | 14 +- .../util/exception/command/command.es6 | 12 +- src/commands/util/exception/exception.es6 | 8 +- .../util/exception/proxy/interface.es6 | 15 +- src/commands/util/{mixins.js => mixins.es6} | 4 +- src/commands/util/proxy/json.es6 | 4 +- test/commands/util/mixins.spec.es6 | 130 ++++++++++++++++++ test/commands/util/proxy/json.spec.es6 | 9 +- 8 files changed, 176 insertions(+), 20 deletions(-) rename src/commands/util/{mixins.js => mixins.es6} (96%) create mode 100644 test/commands/util/mixins.spec.es6 diff --git a/src/commands/util/exception/adt/queue.es6 b/src/commands/util/exception/adt/queue.es6 index 68a589f..83d63c3 100644 --- a/src/commands/util/exception/adt/queue.es6 +++ b/src/commands/util/exception/adt/queue.es6 @@ -1,5 +1,5 @@ /** -* @module commands.util.adt.exception +* @module commands.util.exception.adt * @author Patricio Ferreira <3dimentionar@gmail.com> **/ import _ from 'underscore'; @@ -13,11 +13,17 @@ import Exception from '../exception'; export default class QueueException extends Exception { /** - * Exception Name + * Constructor * @public - * @type {String} + * @override + * @param [...args] {Any} constructor attribute + * @return {commands.util.exception.adt.QueueException} **/ - name = 'QueueException'; + constructor(...args) { + super(...args); + this.name = 'QueueException'; + return this; + } /** * Command Exception types diff --git a/src/commands/util/exception/command/command.es6 b/src/commands/util/exception/command/command.es6 index eac851c..bd20a4b 100644 --- a/src/commands/util/exception/command/command.es6 +++ b/src/commands/util/exception/command/command.es6 @@ -13,11 +13,17 @@ import Exception from '../exception'; export default class CommandException extends Exception { /** - * Exception Name + * Constructor * @public - * @type {String} + * @override + * @param [...args] {Any} constructor attribute + * @return {commands.util.exception.command.CommandException} **/ - name = 'CommandException'; + constructor(...args) { + super(...args); + this.name = 'CommandException'; + return this; + } /** * Command Exception types diff --git a/src/commands/util/exception/exception.es6 b/src/commands/util/exception/exception.es6 index d4fa6a6..2471213 100644 --- a/src/commands/util/exception/exception.es6 +++ b/src/commands/util/exception/exception.es6 @@ -12,13 +12,6 @@ import Logger from '../logger/logger'; **/ export default class Exception extends Error { - /** - * Exception Name - * @public - * @type {String} - **/ - name = 'Exception'; - /** * Constructor * @public @@ -29,6 +22,7 @@ export default class Exception extends Error { **/ constructor(message, ...args) { super(message); + this.name = 'Exception'; Error.captureStackTrace(this, this.constructor); return this; } diff --git a/src/commands/util/exception/proxy/interface.es6 b/src/commands/util/exception/proxy/interface.es6 index c3d34b7..2b946f9 100644 --- a/src/commands/util/exception/proxy/interface.es6 +++ b/src/commands/util/exception/proxy/interface.es6 @@ -12,12 +12,25 @@ import Exception from '../exception'; **/ export default class InterfaceException extends Exception { + /** + * Constructor + * @public + * @override + * @param [...args] {Any} constructor attribute + * @return {commands.util.exception.proxy.InterfaceException} + **/ + constructor(...args) { + super(...args); + this.name = 'InterfaceException'; + return this; + } + /** * Command Exception types * @public * @type {Object} **/ - static type = extend(true, Exception.type, { + static type = extend(true, {}, Exception.type, { proxy: _.template(`Proxies require a 'target' in which their interface operates on`) }); diff --git a/src/commands/util/mixins.js b/src/commands/util/mixins.es6 similarity index 96% rename from src/commands/util/mixins.js rename to src/commands/util/mixins.es6 index c90996e..a565879 100644 --- a/src/commands/util/mixins.js +++ b/src/commands/util/mixins.es6 @@ -43,7 +43,7 @@ _.mixin({ if(_.isRealObject(v)) return _.parametrize(v, pf, k); if(_.isArray(v)) v = v.join(','); return [`${pf}${_s.ltrim(_s(dk + ' ' + k).dasherize().value(), '-')}`, v]; - }, this).flatten().compact().value(); + }, this).flatten().filter((v, k) => _.defined(v)).value(); }, /** @@ -92,3 +92,5 @@ _.mixin({ } }); + +export default _; diff --git a/src/commands/util/proxy/json.es6 b/src/commands/util/proxy/json.es6 index 9d80bf8..bf8b12b 100644 --- a/src/commands/util/proxy/json.es6 +++ b/src/commands/util/proxy/json.es6 @@ -3,6 +3,7 @@ * @author Patricio Ferreira <3dimentionar@gmail.com> **/ import _ from 'underscore'; +import InterfaceException from 'commands/util/exception/proxy/interface'; /** * Interface JSON @@ -79,8 +80,7 @@ class Json { * @return {commands.util.proxy.Json} **/ static proxy(target, ...args) { - if(!_.defined(target)) - throw InterfaceException.new({ type: 'proxy', level: InterfaceException.fatal }); + if(!_.defined(target)) throw InterfaceException.new('proxy'); return new Proxy(target, new this(...args)); } diff --git a/test/commands/util/mixins.spec.es6 b/test/commands/util/mixins.spec.es6 new file mode 100644 index 0000000..d57d23f --- /dev/null +++ b/test/commands/util/mixins.spec.es6 @@ -0,0 +1,130 @@ +/** +* @module commands.util +* @author Patricio Ferreira <3dimentionar@gmail.com> +**/ +import _ from 'commands/util/mixins'; + +describe('commands.util.mixins', function() { + + before(() => { + this.sandbox = sinon.sandbox.create(); + this.test = { + str: 'one', + num: 1, + obj: { + 'obj-str': 'two', + '-obj-num': 2, + bool: false + }, + arr: ['one', 1, true], + bool: true, + nul: null + }; + }); + + beforeEach(() => { + this.mockStatic = this.sandbox.mock(_); + }); + + afterEach(() => { + this.mockStatic.verify(); + + this.sandbox.restore(); + + delete this.mockStatic; + }); + + after(() => { + delete this.test; + delete this.sandbox; + }); + + describe('parametrize()', () => { + + it('Should parametrized object structure', () => { + const exp = _.parametrize(this.test); + assert.include(exp, '--str'); + assert.include(exp, '--num'); + assert.include(exp, '--obj-obj-str'); + assert.include(exp, '--obj-obj-num'); + assert.include(exp, '--obj-bool'); + assert.include(exp, '--arr'); + assert.include(exp, '--bool'); + assert.include(exp, '--nul'); + }); + + it('Should return empty array (empty object)', () => { + assert.lengthOf(_.parametrize(), 0); + }); + + }); + + describe('isRealObject()', () => { + + it('Should return true', () => { + assert.isTrue(_.isRealObject({})); + }); + + it('Should return false', () => { + assert.isFalse(_.isRealObject(new RegExp())); + assert.isFalse(_.isRealObject([])); + assert.isFalse(_.isRealObject("String")); + assert.isFalse(_.isRealObject(1)); + }); + + }); + + describe('isAdt()', () => { + + it('Should return true', () => { + assert.isTrue(_.isAdt({})); + assert.isTrue(_.isAdt([])); + }); + + it('Should return false', () => { + assert.isFalse(_.isAdt()); + assert.isFalse(_.isAdt(1)); + assert.isFalse(_.isAdt("string")); + assert.isFalse(_.isAdt(new RegExp())); + }); + + }); + + describe('instanceOf()', () => { + + it('Should return true', () => { + assert.isTrue(_.instanceOf({}, Object)); + assert.isTrue(_.instanceOf([], Array)); + assert.isTrue(_.instanceOf([], Object)); + assert.isTrue(_.instanceOf(new Number(1), Number)); + assert.isTrue(_.instanceOf(new String("string"), String)); + }); + + it('Should return false', () => { + assert.isFalse(_.instanceOf()); + assert.isFalse(_.instanceOf({}, Array)); + assert.isFalse(_.instanceOf([], String)); + assert.isFalse(_.instanceOf(new Number(1), String)); + assert.isFalse(_.instanceOf(new String("string"), RegExp)); + }); + + }); + + describe('defined()', () => { + + it('Should return true', () => { + assert.isTrue(_.defined(-1)); + assert.isTrue(_.defined(0)); + assert.isTrue(_.defined(false)); + assert.isTrue(_.defined("")); + assert.isTrue(_.defined(NaN)); + }); + + it('Should return false', () => { + assert.isFalse(_.defined(null)); + assert.isFalse(_.defined(undefined)); + }); + + }); + +}); diff --git a/test/commands/util/proxy/json.spec.es6 b/test/commands/util/proxy/json.spec.es6 index 24458c6..8e10278 100644 --- a/test/commands/util/proxy/json.spec.es6 +++ b/test/commands/util/proxy/json.spec.es6 @@ -5,6 +5,7 @@ import _ from 'underscore'; import Json from 'commands/util/proxy/json'; import Command from 'commands/command'; +import InterfaceException from 'commands/util/exception/proxy/interface'; describe('commands.util.proxy.Json', function() { @@ -41,15 +42,19 @@ describe('commands.util.proxy.Json', function() { delete this.sandbox; }); - describe('#constructor', () => { + describe('constructor()', () => { it('Should get a new instance', () => { assert.instanceOf(Json.proxy(this.target), Function); }); + it('Should Error: target is not defined', () => { + assert.throws(() => Json.proxy(), InterfaceException.type.proxy()); + }); + }); - describe('#toJSON()', () => { + describe('toJSON()', () => { it('Should return a json representation', () => { const o = this.target(); From dfd704803697529227d77d9374aed3d01902fb68 Mon Sep 17 00:00:00 2001 From: kuakman <3dimentionar@gmail.com> Date: Sat, 4 Mar 2017 14:52:22 -0800 Subject: [PATCH 09/39] util: checkpoint - Added implementation for commands.util.adt.QueueAsync and commands.util.adt.StackAsync. Added babel-polyfill dependency to have support for async/await and Promises. --- mocha.opts | 1 + package.json | 1 + src/commands/command.es6 | 49 +++++--- src/commands/util/adt/collection.es6 | 4 +- src/commands/util/adt/queue-async.es6 | 132 ++++++++++++++++++++ src/commands/util/adt/queue.es6 | 7 +- src/commands/util/adt/stack-async.es6 | 132 ++++++++++++++++++++ src/commands/util/adt/stack.es6 | 10 -- src/commands/util/proxy/async.es6 | 66 ++++++++++ src/commands/util/proxy/json.es6 | 18 ++- test/commands/util/adt/queue-async.spec.es6 | 60 +++++++++ 11 files changed, 448 insertions(+), 32 deletions(-) create mode 100644 src/commands/util/adt/queue-async.es6 create mode 100644 src/commands/util/adt/stack-async.es6 create mode 100644 src/commands/util/proxy/async.es6 create mode 100644 test/commands/util/adt/queue-async.spec.es6 diff --git a/mocha.opts b/mocha.opts index bc6d910..0f69c27 100644 --- a/mocha.opts +++ b/mocha.opts @@ -1,5 +1,6 @@ --require ./test/global --require babel-register +--require babel-polyfill --recursive --ui bdd --timeout 500 diff --git a/package.json b/package.json index e481931..099356d 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "babel-cli": "6.23.0", "babel-plugin-istanbul": "4.0.0", "babel-plugin-module-resolver": "2.5.0", + "babel-polyfill": "6.23.0", "babel-preset-es2015": "6.22.0", "babel-preset-stage-2": "6.22.0", "babel-register": "6.23.0", diff --git a/src/commands/command.es6 b/src/commands/command.es6 index 8da7f77..f69217d 100644 --- a/src/commands/command.es6 +++ b/src/commands/command.es6 @@ -16,6 +16,7 @@ import CommandException from './util/exception/command/command'; * @extends {events.EventEmitter} * * @uses {commands.util.proxy.JSON} +* @uses {commands.util.proxy.Asynchronous} **/ export default class Command extends EventEmitter { @@ -27,23 +28,20 @@ export default class Command extends EventEmitter { **/ constructor(args) { super(); - return JSON.proxy(extend(true, this, this.defaults, _.pick(args, this.constructor.options), { - _commands: new Collection([], { interface: Command }) - })); + return JSON.proxy(extend(true, this, this.defaults, _.pick(args, this.constructor.options))); } /** - * Command Chainning + * Proxified asynchronous next strategy * @public - * @throws {commands.util.exceptions.CommandException} - * @param command {commands.Command} command used to chain - * @return {commands.Command} - **/ - chain(command) { - if(!_.defined(command) || !_.instanceOf(command, Command)) - throw CommandException.new({ type: 'chain', level: CommandException.fatal }); - this._commands.add(command); - return this; + * @param adt {commands.util.proxy.Asynchronous} adt used for asynchronous operations + * @param resolve {Function} asynchronous promise's resolve + * @param reject {Function} asynchronous promise's reject + * @return {Promise} + **/ + next(adt, resolve, reject) { + this.once(Command.events.done, resolve); + return this.run(); } /** @@ -52,7 +50,26 @@ export default class Command extends EventEmitter { * @return {commands.Command} **/ run() { - return this; + this.pending(); + return this.done(); + } + + /** + * Command Pending exeuction state + * @public + * @return {command.Command} + **/ + pending() { + return this.emit(Command.events.pending, this); + } + + /** + * Command Done exeuction state + * @public + * @return {command.Command} + **/ + done() { + return this.emit(Command.events.done, this); } /** @@ -151,8 +168,8 @@ export default class Command extends EventEmitter { * @type {Object} **/ static events = { - start: 'commands:command:start', - end: 'commands:command:end' + pending: 'commands:command:pending', + done: 'commands:command:done' }; /** diff --git a/src/commands/util/adt/collection.es6 b/src/commands/util/adt/collection.es6 index d350f3a..6ba384e 100644 --- a/src/commands/util/adt/collection.es6 +++ b/src/commands/util/adt/collection.es6 @@ -82,7 +82,9 @@ class Collection extends EventEmitter { * @return {Any} **/ _new(e, opts) { - return this._when(e, () => _.defined(opts.new) ? opts.new(e) : new this._interface(e), () => e); + return this._when(e, + () => _.defined(opts.new && !this.hasInterface()) ? opts.new(e) : new this._interface(e), + () => e); } /** diff --git a/src/commands/util/adt/queue-async.es6 b/src/commands/util/adt/queue-async.es6 new file mode 100644 index 0000000..73fbea9 --- /dev/null +++ b/src/commands/util/adt/queue-async.es6 @@ -0,0 +1,132 @@ +/** +* @module commands.util.adt +* @author Patricio Ferreira <3dimentionar@gmail.com> +**/ +import _ from 'underscore'; +import extend from 'extend'; +import Queue from 'commands/util/adt/queue'; +import Asynchronous from 'commands/util/proxy/async'; + +/** +* Class QueueAsync +* Defines the interface of an asynchronous FIFO Queue (FirstIn-FirstOut). +* Interface for objects on this queue, must implement method `next` of {@link commands.util.proxy.Asynchronous} +* for promise resolution. +* @example +*
Usage
+* +* let myqueue = QueueAsync.new([1,2,3], { capacity: 3, interface: [Class] }) +* .on(QueueAsync.events.next, (element) => console.log('Next: ', element)) +* .on(QueueAsync.events.end, (results) => console.log('End: ', results)) +* const mypromise = p.poll(); // asynchronous +* @extends commands.util.adt.Queue +**/ +class QueueAsync extends Queue { + + /** + * Constructor + * @public + * @override + * @param [initial = []] {Array} Initial Array + * @param [opts = {}] {Object} collection options + * @return {commands.util.adt.QueueAsync} + **/ + constructor(initial = [], opts = {}) { + super(initial, opts); + return extend(true, this, { _last: [] }); + } + + /** + * Default instanciation strategy for new elements added in this collection + * @private + * @override + * @param e {Any} element to instanciate + * @param opts {Object} additional options + * @return {Any} + **/ + _new(e, opts) { + return Asynchronous.proxy(super._new(e, opts), this); + } + + /** + * Resets Last Results + * @public + * @return {commands.util.adt.QueueAsync} + **/ + _resetLast() { + this._last = []; + return this; + } + + /** + * Retrieves and removes the head of this queue, or returns null if this queue is empty + * @public + * @async + * @override + * @param [opts = {}] {Object} additional options + * @return {Promise} + **/ + async poll(opts = {}) { + const res = await this.next(opts); + return this.onNext(res, opts); + } + + /** + * Asynchronous Queue next + * @public + * @emits {QueueAsync.events.next} - when opts.silent is false or undefined + * @param [opts = {}] {Object} additional options + * @return {Promise} + **/ + next(opts) { + let element = super.poll(opts); + if(!opts.silent) this.emit(QueueAsync.events.next, element); + return element.do(this); + } + + /** + * Retrieves and removes the head of this queue, or returns null if this queue is empty + * @public + * @param res {Promise} current promise (resolved or rejected) + * @param [opts = {}] {Object} additional options + * @return {Any} + **/ + onNext(res, opts) { + this._last.push(res); + return this.isEmpty() ? this.end(opts) : this.poll(opts); + } + + /** + * Asynchronous Queue end + * @public + * @emits {QueueAsync.events.end} - when opts.silent is false or undefined + * @param [opts = {}] {Object} additional options + * @return {commands.util.adt.QueueAsync} + **/ + end(opts) { + if(!opts.silent) this.emit(QueueAsync.events.end, this._last); + return this._resetLast(); + } + + /** + * Queue Events + * @static + * @type {Object} + **/ + static events = extend(false, {}, Queue.events, { + + /** + * @event next + **/ + next: 'commands:util:adt:queue-async:next', + + /** + * @event end + **/ + end: 'commands:util:adt:queue-async:end' + + }); + +} + +export default QueueAsync; diff --git a/src/commands/util/adt/queue.es6 b/src/commands/util/adt/queue.es6 index e17632a..9f1c79c 100644 --- a/src/commands/util/adt/queue.es6 +++ b/src/commands/util/adt/queue.es6 @@ -22,14 +22,15 @@ import QueueException from 'commands/util/exception/adt/queue'; * myqueue.poll(); * @extends commands.util.adt.Collection **/ -export default class Queue extends Collection { +class Queue extends Collection { /** * Constructor * @public + * @override * @param [initial = []] {Array} Initial Array * @param [opts = {}] {Object} collection options - * @return {commands.util.adt.Collection} + * @return {commands.util.adt.Queue} **/ constructor(initial = [], opts = {}) { const { capacity } = opts; @@ -130,3 +131,5 @@ export default class Queue extends Collection { }); } + +export default Queue; diff --git a/src/commands/util/adt/stack-async.es6 b/src/commands/util/adt/stack-async.es6 new file mode 100644 index 0000000..c6dfb7d --- /dev/null +++ b/src/commands/util/adt/stack-async.es6 @@ -0,0 +1,132 @@ +/** +* @module commands.util.adt +* @author Patricio Ferreira <3dimentionar@gmail.com> +**/ +import _ from 'underscore'; +import extend from 'extend'; +import Stack from 'commands/util/adt/stack'; +import Asynchronous from 'commands/util/proxy/async'; + +/** +* Class StackAsync +* Defines the interface of a Stack LIFO (LastIn-FirstOut) +* Interface for objects on this stack, must implement method `next` of {@link commands.util.proxy.Asynchronous} +* for promise resolution. +* @example +*
Usage
+* +* let mystack = StackAsync.new([1,2,3], { interface: [Class] }) +* .on(StackAsync.events.next, (element) => console.log('Next: ', element)) +* .on(StackAsync.events.end, (results) => console.log('End: ', results)) +* const mypromise = mystack.pop(); // asynchronous +* @extends commands.util.adt.Stack +**/ +class StackAsync extends Stack { + + /** + * Constructor + * @public + * @override + * @param [initial = []] {Array} Initial Array + * @param [opts = {}] {Object} collection options + * @return {commands.util.adt.StackAsync} + **/ + constructor(initial = [], opts = {}) { + super(initial, opts); + return extend(true, this, { _last: [] }); + } + + /** + * Default instanciation strategy for new elements added in this collection + * @private + * @override + * @param e {Any} element to instanciate + * @param opts {Object} additional options + * @return {Any} + **/ + _new(e, opts) { + return Asynchronous.proxy(super._new(e, opts), this); + } + + /** + * Resets Last Results + * @public + * @return {commands.util.adt.StackAsync} + **/ + _resetLast() { + this._last = []; + return this; + } + + /** + * Retrieves and removes the head of this stack, or returns null if this stack is empty + * @public + * @async + * @override + * @param [opts = {}] {Object} additional options + * @return {Promise} + **/ + async pop(opts) { + const res = await this.next(opts); + return this.onNext(res, opts); + } + + /** + * Asynchronous Queue next + * @public + * @emits {StackAsync.events.next} - when opts.silent = false or undefined + * @param [opts = {}] {Object} additional options + * @return {Promise} + **/ + next(opts = {}) { + let element = super.pop(opts); + if(!opts.silent) this.emit(StackAsync.events.next, element); + return element.do(this); + } + + /** + * Retrieves and removes the head of this queue, or returns null if this queue is empty + * @public + * @param res {Promise} promise reference with resolution (resolved or rejected) + * @param [opts = {}] {Object} additional options + * @return {Promise} + **/ + onNext(res, opts) { + this._last.push(res); + return this.isEmpty() ? this.end(opts) : this.pop(opts); + } + + /** + * Asynchronous Queue end + * @public + * @emits {StackAsync.events.end} - when opts.silent is false or undefined + * @param [opts = {}] {Object} additional options + * @return {commands.util.adt.StackAsync} + **/ + end(opts) { + if(!opts.silent) this.emit(StackAsync.events.end, this._last); + return this._resetLast(); + } + + /** + * Queue Events + * @static + * @type {Object} + **/ + static events = extend(false, {}, Stack.events, { + + /** + * @event next + **/ + next: 'commands:util:adt:stack-async:next', + + /** + * @event end + **/ + end: 'commands:util:adt:stack-async:end' + + }); + +} + +export default StackAsync; diff --git a/src/commands/util/adt/stack.es6 b/src/commands/util/adt/stack.es6 index 57ec2c2..92cdcfb 100644 --- a/src/commands/util/adt/stack.es6 +++ b/src/commands/util/adt/stack.es6 @@ -99,16 +99,6 @@ class Stack extends Collection { pop: 'commands:util:adt:stack:pop' }); - /** - * Static constructor - * @static - * @param [...args] {Any} Constructor arguments - * @return {commands.util.adt.Stack} - **/ - static new(...args) { - return new this(...args); - } - } export default Stack; diff --git a/src/commands/util/proxy/async.es6 b/src/commands/util/proxy/async.es6 new file mode 100644 index 0000000..8ff61ee --- /dev/null +++ b/src/commands/util/proxy/async.es6 @@ -0,0 +1,66 @@ +/** +* @module commands.util.proxy +* @author Patricio Ferreira <3dimentionar@gmail.com> +**/ +import { EventEmitter } from 'events'; +import _ from 'underscore'; +import InterfaceException from 'commands/util/exception/proxy/interface'; + +/** +* Interface Asynchronous +* @extends events.EventEmitter +**/ +class Asynchronous extends EventEmitter { + + /** + * Proxy's `get` trap strategy + * @public + * @param target {Any} proxy target + * @param property {String} property name + * @param receiver {Any} proxy receiver + * @return {Any} + **/ + get(target, property) { + let value = _.defined(this[property]) ? this[property] : target[property]; + return _.isFunction(value) ? this._context(target, property, value) : value; + } + + /** + * Resolves proxified function binding with context + * @private + * @param target {Any} proxy target + * @param property {String} property name + * @param func {Function} proxified function + * @return {Function} + **/ + _context(target, property, func) { + return _.defined(this[property]) ? _.bind(func, target, this) : _.bind(func, target); + } + + /** + * Default strategy to perform an asynchronous operation + * @public + * @param [ctx] {commands.util.proxy.Asynchronous} context reference + * @param adt {commands.util.proxy.Asynchronous} reference to adt using this proxy on their elements + * @return {Promise} + **/ + do(ctx, adt) { + return new Promise((resolve, reject) => this.next(adt, resolve, reject)); + } + + /** + * Proxifies a given target with an instance of this class + * @static + * @throws {commands.util.exception.proxy.InterfaceException} + * @param target {Any} instance to proxify + * @param [...args] {Any} constructor arguments + * @return {commands.util.proxy.Asynchronous} + **/ + static proxy(target, ...args) { + if(!_.defined(target)) throw InterfaceException.new('proxy'); + return new Proxy(target, new this(...args)); + } + +} + +export default Asynchronous; diff --git a/src/commands/util/proxy/json.es6 b/src/commands/util/proxy/json.es6 index bf8b12b..6e7cb7e 100644 --- a/src/commands/util/proxy/json.es6 +++ b/src/commands/util/proxy/json.es6 @@ -1,5 +1,5 @@ /** -* @module commands.util.interface +* @module commands.util.proxy * @author Patricio Ferreira <3dimentionar@gmail.com> **/ import _ from 'underscore'; @@ -30,7 +30,19 @@ class Json { **/ get(target, property, receiver) { let value = _.defined(this[property]) ? this[property] : target[property]; - return _.isFunction(value) ? _.bind(value, target, this) : value; + return _.isFunction(value) ? this._context(target, property, value) : value; + } + + /** + * Resolves proxified function binding with context + * @private + * @param target {Any} proxy target + * @param property {String} property name + * @param func {Function} proxified function + * @return {Function} + **/ + _context(target, property, func) { + return _.defined(this[property]) ? _.bind(func, target, this) : _.bind(func, target); } /** @@ -64,7 +76,7 @@ class Json { * Returns a json representation of the instance of this class * This method uses recursion * @public - * @param ctx {commands.util.proxy.Json} reference to the instance of this class + * @param [ctx] {commands.util.proxy.Json} context reference * @return {Object} **/ toJSON(ctx) { diff --git a/test/commands/util/adt/queue-async.spec.es6 b/test/commands/util/adt/queue-async.spec.es6 new file mode 100644 index 0000000..fab0dbc --- /dev/null +++ b/test/commands/util/adt/queue-async.spec.es6 @@ -0,0 +1,60 @@ +/** +* @module commands.util.adt +* @author Patricio Ferreira <3dimentionar@gmail.com> +**/ +import QueueAsync from 'commands/util/adt/queue-async'; +import Command from 'commands/command'; +import QueueException from 'commands/util/exception/adt/queue'; + +describe('commands.util.adt.QueueAsync', function() { + + before(() => { + this.sandbox = sinon.sandbox.create(); + }); + + beforeEach(() => { + this.mockStatic = this.sandbox.mock(QueueAsync); + this.mockProto = this.sandbox.mock(QueueAsync.prototype); + }); + + afterEach(() => { + this.mockStatic.verify(); + this.mockProto.verify(); + + this.sandbox.restore(); + + delete this.mockStatic; + delete this.mockProto; + }); + + after(() => { + delete this.sandbox; + }); + + describe('constructor()', () => { + + it('Should get a new instance', () => { + assert.instanceOf(QueueAsync.new([], { capacity: 3 }), QueueAsync); + }); + + }); + + describe('async->start()', () => { + + it('Should start asynchronous queue', (done) => { + const exp = QueueAsync.new([{ env: 'dev' }, { env: 'stage' }], { capacity: 2, interface: Command }); + + exp.on(QueueAsync.events.next, (element) => { + // TODO: assertions + }); + exp.on(QueueAsync.events.end, (result) => { + // TODO: assertions + done(); + }); + + assert.instanceOf(exp.poll(), Promise); + }); + + }); + +}); From 1974ea36d52c07fe25db99aa3ff704f20b52adff Mon Sep 17 00:00:00 2001 From: kuakman <3dimentionar@gmail.com> Date: Sat, 4 Mar 2017 17:39:57 -0800 Subject: [PATCH 10/39] util: checkpoint - added unit test coverage to commands.util.adt.QueueAsync. --- src/commands/util/adt/queue-async.es6 | 3 ++ src/commands/util/adt/queue.es6 | 2 +- src/commands/util/exception/adt/stack.es6 | 0 .../util/exception/proxy/interface.es6 | 3 +- test/commands/util/adt/queue-async.spec.es6 | 43 ++++++++++++++++--- 5 files changed, 44 insertions(+), 7 deletions(-) create mode 100644 src/commands/util/exception/adt/stack.es6 diff --git a/src/commands/util/adt/queue-async.es6 b/src/commands/util/adt/queue-async.es6 index 73fbea9..42dbe33 100644 --- a/src/commands/util/adt/queue-async.es6 +++ b/src/commands/util/adt/queue-async.es6 @@ -5,6 +5,7 @@ import _ from 'underscore'; import extend from 'extend'; import Queue from 'commands/util/adt/queue'; +import InterfaceException from 'commands/util/exception/proxy/interface'; import Asynchronous from 'commands/util/proxy/async'; /** @@ -80,6 +81,8 @@ class QueueAsync extends Queue { **/ next(opts) { let element = super.poll(opts); + if(!_.defined(element.next)) + throw InterfaceException.new('interface', { name: 'commands.util.proxy.Asynchronous' }); if(!opts.silent) this.emit(QueueAsync.events.next, element); return element.do(this); } diff --git a/src/commands/util/adt/queue.es6 b/src/commands/util/adt/queue.es6 index 9f1c79c..7b50299 100644 --- a/src/commands/util/adt/queue.es6 +++ b/src/commands/util/adt/queue.es6 @@ -48,7 +48,7 @@ class Queue extends Collection { _valid(element) { const { capacity } = this; if(_.isArray(element) && (element.length > capacity)) - throw QueueException.new('capacityViolation', { level: QueueException.fatal, capacity }); + throw QueueException.new('capacityViolation', { capacity }); return super._valid(element); } diff --git a/src/commands/util/exception/adt/stack.es6 b/src/commands/util/exception/adt/stack.es6 new file mode 100644 index 0000000..e69de29 diff --git a/src/commands/util/exception/proxy/interface.es6 b/src/commands/util/exception/proxy/interface.es6 index 2b946f9..a91030a 100644 --- a/src/commands/util/exception/proxy/interface.es6 +++ b/src/commands/util/exception/proxy/interface.es6 @@ -31,7 +31,8 @@ export default class InterfaceException extends Exception { * @type {Object} **/ static type = extend(true, {}, Exception.type, { - proxy: _.template(`Proxies require a 'target' in which their interface operates on`) + proxy: _.template(`Proxies require a 'target' in which their interface operates on`), + interface: _.template(`Instance is required to implement [<%= name %>]`) }); } diff --git a/test/commands/util/adt/queue-async.spec.es6 b/test/commands/util/adt/queue-async.spec.es6 index fab0dbc..eb06bec 100644 --- a/test/commands/util/adt/queue-async.spec.es6 +++ b/test/commands/util/adt/queue-async.spec.es6 @@ -4,7 +4,7 @@ **/ import QueueAsync from 'commands/util/adt/queue-async'; import Command from 'commands/command'; -import QueueException from 'commands/util/exception/adt/queue'; +import InterfaceException from 'commands/util/exception/proxy/interface'; describe('commands.util.adt.QueueAsync', function() { @@ -37,24 +37,57 @@ describe('commands.util.adt.QueueAsync', function() { assert.instanceOf(QueueAsync.new([], { capacity: 3 }), QueueAsync); }); + it('Should get a new instance (no initial and no opts)', () => { + assert.instanceOf(QueueAsync.new(), QueueAsync); + }); + }); - describe('async->start()', () => { + describe('async->poll()', () => { - it('Should start asynchronous queue', (done) => { + it('Should poll all elements asynchronously from queue (with Events)', (done) => { const exp = QueueAsync.new([{ env: 'dev' }, { env: 'stage' }], { capacity: 2, interface: Command }); exp.on(QueueAsync.events.next, (element) => { - // TODO: assertions + assert.instanceOf(element, Command); }); + exp.on(QueueAsync.events.end, (result) => { - // TODO: assertions + assert.isArray(result); + assert.lengthOf(result, 2); + assert.instanceOf(result[0], Command); + done(); }); assert.instanceOf(exp.poll(), Promise); }); + it('Should poll all elements asynchronously from queue (without Events)', (done) => { + const exp = QueueAsync.new([], { capacity: 2, interface: Command }); + const expEmit = this.mockProto.expects('emit').never(); + + assert.isTrue(exp.offer({ env: 'dev' }, { silent: true })); + assert.isTrue(exp.offer({ env: 'stage' }, { silent: true })); + + const result = exp.poll({ silent: true }).then((queue) => { + assert.instanceOf(queue, QueueAsync); + assert.isTrue(queue.isEmpty()); + done(); + }).catch((err) => console.log(err)); + }); + + it('Should Error: Element doesn\'t implement commands.util.proxy.Asynchronous#next()', (done) => { + const message = InterfaceException.type.interface({ name: 'commands.util.proxy.Asynchronous' }); + const exp = QueueAsync.new([{ simple: true }], { capacity: 1 }); + + exp.poll().catch((err) => { + assert.instanceOf(err, Error); + assert.equal(err.message, message); + done(); + }); + }); + }); }); From cbbca47f4c5157e87d97b4044a0c33a4e46d1cce Mon Sep 17 00:00:00 2001 From: kuakman <3dimentionar@gmail.com> Date: Sun, 5 Mar 2017 14:52:10 -0800 Subject: [PATCH 11/39] util: checkpoint - Added unit test coverage to commands.util.adt.StackAsync and commands.util.proxy.Asynchronous. --- src/commands/util/adt/collection.es6 | 3 +- src/commands/util/adt/queue-async.es6 | 4 +- src/commands/util/adt/queue.es6 | 10 +-- src/commands/util/adt/stack-async.es6 | 11 ++- src/commands/util/adt/stack.es6 | 13 +-- test/commands/util/adt/collection.spec.es6 | 17 ++-- test/commands/util/adt/stack-async.spec.es6 | 93 +++++++++++++++++++++ test/commands/util/adt/stack.spec.es6 | 7 +- test/commands/util/proxy/async.spec.es6 | 54 ++++++++++++ 9 files changed, 181 insertions(+), 31 deletions(-) create mode 100644 test/commands/util/adt/stack-async.spec.es6 create mode 100644 test/commands/util/proxy/async.spec.es6 diff --git a/src/commands/util/adt/collection.es6 b/src/commands/util/adt/collection.es6 index 6ba384e..dcbfc48 100644 --- a/src/commands/util/adt/collection.es6 +++ b/src/commands/util/adt/collection.es6 @@ -186,7 +186,8 @@ class Collection extends EventEmitter { **/ removeAt(ix = 0, opts = {}) { if(!this._valid(ix) || !_.isNumber(ix) || ix > (this.size() - 1)) return null; - this._fire(Collection.events.remove, opts, this._collection.splice(ix, 1)); + this._collection.splice(ix, 1); + this._fire(Collection.events.remove, opts, ix); return ix; } diff --git a/src/commands/util/adt/queue-async.es6 b/src/commands/util/adt/queue-async.es6 index 42dbe33..52e3dc7 100644 --- a/src/commands/util/adt/queue-async.es6 +++ b/src/commands/util/adt/queue-async.es6 @@ -76,7 +76,7 @@ class QueueAsync extends Queue { * Asynchronous Queue next * @public * @emits {QueueAsync.events.next} - when opts.silent is false or undefined - * @param [opts = {}] {Object} additional options + * @param [opts] {Object} additional options * @return {Promise} **/ next(opts) { @@ -91,7 +91,7 @@ class QueueAsync extends Queue { * Retrieves and removes the head of this queue, or returns null if this queue is empty * @public * @param res {Promise} current promise (resolved or rejected) - * @param [opts = {}] {Object} additional options + * @param [opts] {Object} additional options * @return {Any} **/ onNext(res, opts) { diff --git a/src/commands/util/adt/queue.es6 b/src/commands/util/adt/queue.es6 index 7b50299..eb11ae0 100644 --- a/src/commands/util/adt/queue.es6 +++ b/src/commands/util/adt/queue.es6 @@ -105,12 +105,10 @@ class Queue extends Collection { * @return {Object} **/ poll(opts = {}) { - if(this.size() > 0) { - let polled = this.remove(this.get(0), { silent: true }); - this._fire(Queue.events.poll, opts, polled); - return polled; - } - return null; + if(this.size() === 0) return null; + let polled = this.remove(this.get(0), { silent: true }); + this._fire(Queue.events.poll, opts, polled); + return polled; } /** diff --git a/src/commands/util/adt/stack-async.es6 b/src/commands/util/adt/stack-async.es6 index c6dfb7d..6142483 100644 --- a/src/commands/util/adt/stack-async.es6 +++ b/src/commands/util/adt/stack-async.es6 @@ -5,6 +5,7 @@ import _ from 'underscore'; import extend from 'extend'; import Stack from 'commands/util/adt/stack'; +import InterfaceException from 'commands/util/exception/proxy/interface'; import Asynchronous from 'commands/util/proxy/async'; /** @@ -66,7 +67,7 @@ class StackAsync extends Stack { * @param [opts = {}] {Object} additional options * @return {Promise} **/ - async pop(opts) { + async pop(opts = {}) { const res = await this.next(opts); return this.onNext(res, opts); } @@ -75,11 +76,13 @@ class StackAsync extends Stack { * Asynchronous Queue next * @public * @emits {StackAsync.events.next} - when opts.silent = false or undefined - * @param [opts = {}] {Object} additional options + * @param [opts] {Object} additional options * @return {Promise} **/ - next(opts = {}) { + next(opts) { let element = super.pop(opts); + if(!_.defined(element.next)) + throw InterfaceException.new('interface', { name: 'commands.util.proxy.Asynchronous' }); if(!opts.silent) this.emit(StackAsync.events.next, element); return element.do(this); } @@ -88,7 +91,7 @@ class StackAsync extends Stack { * Retrieves and removes the head of this queue, or returns null if this queue is empty * @public * @param res {Promise} promise reference with resolution (resolved or rejected) - * @param [opts = {}] {Object} additional options + * @param [opts] {Object} additional options * @return {Promise} **/ onNext(res, opts) { diff --git a/src/commands/util/adt/stack.es6 b/src/commands/util/adt/stack.es6 index 92cdcfb..3ff7d3c 100644 --- a/src/commands/util/adt/stack.es6 +++ b/src/commands/util/adt/stack.es6 @@ -20,7 +20,7 @@ import Collection from 'commands/util/adt/collection'; * * let mystack = new Stack([{ name: 'one' }, { name: 'two' }], { interface: MyClass }); * mystack.push({ name: 3 }); // Adds one more element into the stack -* mystack.pop(); // Outputs { name: 'one' } of MyClass, removing the element +* mystack.pop(); // Outputs { name: 3 } of MyClass, removing the element * @extends commands.util.adt.Collection **/ class Stack extends Collection { @@ -62,13 +62,14 @@ class Stack extends Collection { /** * Retrieves and removes the head of this stack, or returns null if this stack is empty * @public - * @return {Object} + * @param [opts = {}] {Object} additional options + * @return {Any} **/ - pop() { + pop(opts = {}) { if(this.size() <= 0) return null; - let removed = this.removeAt(0, { silent: true }); - this._fire(Stack.events.pop, {}, removed); - return removed; + let popped = this.remove(this.get(this.size() - 1), { silent: true }); + this._fire(Stack.events.pop, opts, popped); + return popped; } /** diff --git a/test/commands/util/adt/collection.spec.es6 b/test/commands/util/adt/collection.spec.es6 index d5ef2a3..70b33c2 100644 --- a/test/commands/util/adt/collection.spec.es6 +++ b/test/commands/util/adt/collection.spec.es6 @@ -243,29 +243,28 @@ describe('commands.util.adt.Collection', function() { describe('removeAt()', () => { it('Should remove an element at index (without interface)', () => { - const toRemove = 2; + const toRemove = 1; const exp = Collection.new([1,2,3]); const expEmit = this.mockCollection.expects('emit') .once() - .withArgs(Collection.events.remove, exp, [toRemove]) + .withArgs(Collection.events.remove, exp, toRemove) .returns(exp); - assert.equal(1, exp.removeAt(1)); + assert.equal(1, exp.removeAt(toRemove)); assert.equal(2, exp.size()); assert.equal(3, exp.get(1)); }); it('Should remove an element at index (with interface)', () => { const exp = Collection.new([{ env: 'staging' }, { env: 'production' }], { interface: Command }); - const toRemove = exp.get(1); + const toRemove = 1; const expEmit = this.mockCollection.expects('emit') .once() - .withArgs(Collection.events.remove, exp, [toRemove]) + .withArgs(Collection.events.remove, exp, toRemove) .returns(exp); - assert.equal(1, exp.removeAt(1)); + assert.equal(toRemove, exp.removeAt(1)); assert.equal(1, exp.size()); - assert.notEqual(toRemove.toJSON(), exp.get(0).toJSON()); }); it('Should NOT remove an element at index (default index)', () => { @@ -391,8 +390,8 @@ describe('commands.util.adt.Collection', function() { const exp = Collection.new([{ env: 'stage' }, { env: 'dev' }, { env: 'prod' }], { interface: Command }); const expEmit = this.mockCollection.expects('emit') .exactly(2) - .withArgs(Collection.events.remove, exp, [sinon.match.instanceOf(Command)]) - .returns(sinon.match.instanceOf(Command)); + .withArgs(Collection.events.remove, exp, sinon.match(0).or(sinon.match(1))) + .returns(sinon.match(0).or(sinon.match(1))); assert.instanceOf(exp.removeBy(spyPredicate), Collection); assert.equal(1, exp.size()); diff --git a/test/commands/util/adt/stack-async.spec.es6 b/test/commands/util/adt/stack-async.spec.es6 new file mode 100644 index 0000000..aa06e3f --- /dev/null +++ b/test/commands/util/adt/stack-async.spec.es6 @@ -0,0 +1,93 @@ +/** +* @module commands.util.adt +* @author Patricio Ferreira <3dimentionar@gmail.com> +**/ +import StackAsync from 'commands/util/adt/stack-async'; +import Command from 'commands/command'; +import InterfaceException from 'commands/util/exception/proxy/interface'; + +describe('commands.util.adt.StackAsync', function() { + + before(() => { + this.sandbox = sinon.sandbox.create(); + }); + + beforeEach(() => { + this.mockStatic = this.sandbox.mock(StackAsync); + this.mockProto = this.sandbox.mock(StackAsync.prototype); + }); + + afterEach(() => { + this.mockStatic.verify(); + this.mockProto.verify(); + + this.sandbox.restore(); + + delete this.mockStatic; + delete this.mockProto; + }); + + after(() => { + delete this.sandbox; + }); + + describe('constructor()', () => { + + it('Should get a new instance', () => { + assert.instanceOf(StackAsync.new([{ option: true }]), StackAsync); + }); + + it('Should get a new instance (no initial and no opts)', () => { + assert.instanceOf(StackAsync.new(), StackAsync); + }); + + }); + + describe('async->pop()', () => { + + it('Should pop all elements asynchronously from the stack (with Events)', (done) => { + const exp = StackAsync.new([{ env: 'dev' }, { env: 'stage' }], { interface: Command }); + + exp.on(StackAsync.events.next, (element) => { + assert.instanceOf(element, Command); + }); + + exp.on(StackAsync.events.end, (result) => { + assert.isArray(result); + assert.lengthOf(result, 2); + assert.instanceOf(result[0], Command); + + done(); + }); + + assert.instanceOf(exp.pop(), Promise); + }); + + it('Should pop all elements asynchronously from the stack (without Events)', (done) => { + const exp = StackAsync.new([], { interface: Command }); + const expEmit = this.mockProto.expects('emit').never(); + + assert.isTrue(exp.push({ env: 'dev' }, { silent: true })); + assert.isTrue(exp.push({ env: 'stage' }, { silent: true })); + + const result = exp.pop({ silent: true }).then((stack) => { + assert.instanceOf(stack, StackAsync); + assert.isTrue(stack.isEmpty()); + done(); + }).catch((err) => console.log(err)); + }); + + it('Should Error: Element doesn\'t implement commands.util.proxy.Asynchronous#next()', (done) => { + const message = InterfaceException.type.interface({ name: 'commands.util.proxy.Asynchronous' }); + const exp = StackAsync.new([{ simple: true }]); + + exp.pop().catch((err) => { + assert.instanceOf(err, Error); + assert.equal(err.message, message); + done(); + }); + }); + + }); + +}); diff --git a/test/commands/util/adt/stack.spec.es6 b/test/commands/util/adt/stack.spec.es6 index b3ef0f1..b07991d 100644 --- a/test/commands/util/adt/stack.spec.es6 +++ b/test/commands/util/adt/stack.spec.es6 @@ -83,13 +83,14 @@ describe('commands.util.adt.Stack', function() { describe('#pop()', () => { it('Should remove and get the first element', () => { - const exp = Stack.new([{ option: true }, { option: false }], { interface: Command }); + const exp = Stack.new([{ env: 'dev' }, { env: 'stage' }], { interface: Command }); + const expPop = exp.last(); const expEmit = this.mockStack.expects('emit') .once() - .withArgs(Stack.events.pop, exp) + .withArgs(Stack.events.pop, exp, expPop) .returns(exp); - assert.equal(0, exp.pop()); + assert.equal(expPop, exp.pop()); assert.instanceOf(exp.peek(), Command); assert.equal(1, exp.size()); }); diff --git a/test/commands/util/proxy/async.spec.es6 b/test/commands/util/proxy/async.spec.es6 new file mode 100644 index 0000000..51ea326 --- /dev/null +++ b/test/commands/util/proxy/async.spec.es6 @@ -0,0 +1,54 @@ +/** +* @module commands.util.proxy +* @author Patricio Ferreira <3dimentionar@gmail.com> +**/ +import _ from 'underscore'; +import Asynchronous from 'commands/util/proxy/async'; +import Command from 'commands/command'; +import InterfaceException from 'commands/util/exception/proxy/interface'; + +describe('commands.util.proxy.Asynchronous', function() { + + before(() => { + this.sandbox = sinon.sandbox.create(); + }); + + beforeEach(() => { + this.target = () => { return { next: (adt, resolve, reject) => {} }; }; + this.mockAsync = this.sandbox.mock(Asynchronous.prototype); + }); + + afterEach(() => { + this.sandbox.restore(); + delete this.target; + delete this.mockAsync; + }); + + after(() => { + delete this.sandbox; + }); + + describe('constructor()', () => { + + it('Should get a new instance', () => { + assert.instanceOf(Asynchronous.proxy(this.target), Function); + }); + + it('Should Error: target is not defined', () => { + assert.throws(() => Asynchronous.proxy(), InterfaceException.type.proxy()); + }); + + }); + + describe('do()', () => { + + it('Should return a decorated target with asynchronous capabilities', () => { + const o = this.target(); + const exp = Asynchronous.proxy(o); + assert.isNotNull(exp.do); + assert.instanceOf(exp.do(o), Promise); + }); + + }); + +}); From ae2af98ba5b47fb1117a093b1da00c1d020c435c Mon Sep 17 00:00:00 2001 From: kuakman <3dimentionar@gmail.com> Date: Sun, 5 Mar 2017 16:53:56 -0800 Subject: [PATCH 12/39] util: checkpoint - Added commands.util.logger.Logger initial implementation. Also, added unit test coverage to it. --- mocha.opts | 2 +- src/commands/util/logger/logger.es6 | 208 ++++++++++++++++++++++ test/commands/util/logger/logger.spec.es6 | 65 +++++++ 3 files changed, 274 insertions(+), 1 deletion(-) create mode 100644 test/commands/util/logger/logger.spec.es6 diff --git a/mocha.opts b/mocha.opts index 0f69c27..c567d39 100644 --- a/mocha.opts +++ b/mocha.opts @@ -3,6 +3,6 @@ --require babel-polyfill --recursive --ui bdd ---timeout 500 +--timeout 200 --reporter spec --compilers es6:babel-register diff --git a/src/commands/util/logger/logger.es6 b/src/commands/util/logger/logger.es6 index e69de29..303d2e2 100644 --- a/src/commands/util/logger/logger.es6 +++ b/src/commands/util/logger/logger.es6 @@ -0,0 +1,208 @@ +/** +* @module commands.util.logger +* @author Patricio Ferreira <3dimentionar@gmail.com> +**/ +import { EventEmitter } from 'events'; +import _ from 'underscore'; +import _s from 'underscore.string'; +import extend from 'extend'; +import chalk from 'chalk'; + +/** +* Class Logger +* @extends events.EventEmitter +**/ +export class Logger extends EventEmitter { + + /** + * Constructor + * @public + * @param {Object} [opts = {}] - constructor options + * @return {commands.util.logger.Logger} + **/ + constructor() { + super(); + return extend(true, this, { _buffer: [] }); + } + + /** + * Proxy's 'getPrototypeOf' trap override + * @public + * @param {Any} target - constructor reference + * @return {Object} + **/ + getPrototypeOf(target) { + return this.constructor.prototype; + } + + /** + * Proxy's 'get' trap override + * @public + * @param {Any} target - logger class reference + * @param {String} property - property to resolve + * @return {commands.util.logger.Logger} + **/ + get(target, property) { + if(_.defined(this[property])) return _.bind(this[property], this); + if(_.defined(chalk[property])) return chalk[property]; + return null; + } + + /** + * Proxy's 'apply' trap override + * @private + * @param {commands.util.logger.Logger} target - logger instance reference + * @param {Any} thisArg - this context reference + * @param {Array} args - arguments list + * @return + **/ + apply(target, thisArg, args) { + this._add(...args); + return Logger.ref; + } + + /** + * Appends Message to the internal buffer + * @public + * @param {String} [message = ''] - message to add + * @param {String} [style = chalk.white] - message style override + * @return {commands.util.logger.Logger} + **/ + _add(message = '') { + this._buffer.push(message); + return this; + } + + /** + * Flushes message buffer + * @private + * @emits {Logger.events.flush} + * @return {commands.util.logger.Logger} + **/ + _flush() { + this._buffer = []; + return this; + } + + /** + * Fires Event + * @public + * @param {String} name - event name + * @param [...args] {Any} - arguments to pass to the event being dispatch + * @return {commands.util.logger.Logger} + **/ + _fire(name, opts) { + if(!opts.silent) this.emit(name, this); + return this; + } + + /** + * Outputs message to the stdout + * @private + * @param {Function} style - chalk style override + * @param {String} [level = Logger.level.debug] - level + * @return {commands.util.logger.Logger} + **/ + _output(style, level = Logger.level.debug) { + level = _.defined(style) ? style : level; + console.log(level(this._buffer.join('\n'))); + return this._flush(); + } + + /** + * Debug message + * @public + * @emits {Logger.events.debug} + * @param {Object} [opts = {}] - additional options + * @return {commands.util.logger.Logger} + **/ + debug(style, opts = {}) { + return this._output(style)._fire(Logger.events.debug, opts); + } + + /** + * Warning message + * @public + * @emits {Logger.events.warning} + * @param {Object} [opts = {}] - additional options + * @return {commands.util.logger.Logger} + **/ + warn(opts = {}) { + return this._output(null, Logger.level.warning)._fire(Logger.events.warning, opts); + } + + /** + * Fatal message + * @public + * @emits {Logger.events.fatal} + * @param {Object} [opts = {}] - additional options + **/ + fatal(opts = {}) { + this._output(null, Logger.level.fatal)._fire(Logger.events.fatal, opts); + process.exit(1); + } + + /** + * Logger Levels + * @static + * @type {Object} + **/ + static level = { + debug: chalk.green, + warning: chalk.yellow, + fatal: chalk.red + } + + /** + * Logger Events + * @static + * @type {Object} + **/ + static events = { + + /** + * @event flush + **/ + flush: 'commands:util:logger:flush', + + /** + * @event debug + **/ + debug: 'commands:util:logger:debug', + + /** + * @event warning + **/ + warning: 'commands:util:logger:warning', + + /** + * @event fatal + **/ + fatal:'commands:util:logger:fatal' + + } + + /** + * Sets an static reference of an instance of this class + * @private + * @static + * @param {commands.util.logger.Logger} logger - logger instance reference + * @return {Any} + **/ + static _instance(logger) { + return (this.ref = logger); + } + + /** + * Static Constructor + * @static + * @param {Any} [...args] - constructor arguments + * @return {commands.util.logger.Logger} + **/ + static new(...args) { + return new this(...args); + } + +} + +export default Logger._instance(new Proxy(Logger, Logger.new())); diff --git a/test/commands/util/logger/logger.spec.es6 b/test/commands/util/logger/logger.spec.es6 new file mode 100644 index 0000000..a06a284 --- /dev/null +++ b/test/commands/util/logger/logger.spec.es6 @@ -0,0 +1,65 @@ +/** +* @module commands.util.logger +* @author Patricio Ferreira <3dimentionar@gmail.com> +**/ +import logger, { Logger } from 'commands/util/logger/logger'; + +describe('commands.util.logger.Logger', function() { + + before(() => { + this.sandbox = sinon.sandbox.create(); + }); + + beforeEach(() => { + this.mockStatic = this.sandbox.mock(Logger); + this.mockProto = this.sandbox.mock(logger); + this.mockProcess = this.sandbox.mock(process); + }); + + afterEach(() => { + this.mockStatic.verify(); + this.mockProto.verify(); + this.mockProcess.verify(); + + this.sandbox.restore(); + + delete this.mockStatic; + delete this.mockProto; + delete this.mockProcess; + }); + + after(() => { + delete this.sandbox; + }); + + describe('constructor()', () => { + + it('Should check the singleton instance', () => { + assert.instanceOf(logger, Logger); + }); + + it('Should adds to the buffer by using constructor function', () => { + logger('hello')('world').debug(logger.magenta); + }); + + }); + + describe('debug()', () => { + + xit('Should debug', () => {}); + + }); + + describe('warn()', () => { + + xit('Should warn', () => {}); + + }); + + describe('fatal()', () => { + + xit('Should fatal', () => {}); + + }); + +}); From 537dc66ab32e8efba3427d183fe5e12daff3312d Mon Sep 17 00:00:00 2001 From: kuakman <3dimentionar@gmail.com> Date: Sun, 5 Mar 2017 17:21:52 -0800 Subject: [PATCH 13/39] util: checkpoint - Added Validation rules per log levels (output = default, debug and silent) to commands.util.logger.Logger. --- src/commands/util/logger/logger.es6 | 78 +++++++++++++++++++++++++---- test/specs/config-basic.json | 3 +- 2 files changed, 71 insertions(+), 10 deletions(-) diff --git a/src/commands/util/logger/logger.es6 b/src/commands/util/logger/logger.es6 index 303d2e2..6037223 100644 --- a/src/commands/util/logger/logger.es6 +++ b/src/commands/util/logger/logger.es6 @@ -10,6 +10,7 @@ import chalk from 'chalk'; /** * Class Logger +* TODO: Added support for debug | silent | output * @extends events.EventEmitter **/ export class Logger extends EventEmitter { @@ -17,11 +18,11 @@ export class Logger extends EventEmitter { /** * Constructor * @public - * @param {Object} [opts = {}] - constructor options * @return {commands.util.logger.Logger} **/ constructor() { super(); + this.level(); return extend(true, this, { _buffer: [] }); } @@ -61,6 +62,25 @@ export class Logger extends EventEmitter { return Logger.ref; } + /** + * Validates level of output provided + * Validation Rules: + * - If level equals `silent` -> will output only fatal logs + * - If level equals `output` -> will output warn, fatal and output (except debug). + * - If level equals `debug` -> will print all logs (warn, fatal, output and debug). + * @public + * @param {Function} type - log's type + * @return {Boolean} + **/ + _validate(type) { + const { fatal, debug } = Logger.type; + const { silent, output, debug } = Logger.level; + if(this._level === silent && _.isEqual(type, fatal)) return true; + if(this._level === output && !_.isEqual(type, debug) return true; + if(this._level === debug) return true; + return false; + } + /** * Appends Message to the internal buffer * @public @@ -103,12 +123,24 @@ export class Logger extends EventEmitter { * @param {String} [level = Logger.level.debug] - level * @return {commands.util.logger.Logger} **/ - _output(style, level = Logger.level.debug) { + _output(style, type = Logger.type.debug) { + if(!this._validate(type)) return this._flush(); level = _.defined(style) ? style : level; console.log(level(this._buffer.join('\n'))); return this._flush(); } + /** + * Output message + * @public + * @emits {Logger.events.debug} + * @param {Object} [opts = {}] - additional options + * @return {commands.util.logger.Logger} + **/ + out(style, opts = {}) { + return this._output(style, Logger.type.output)._fire(Logger.events.output, opts); + } + /** * Debug message * @public @@ -116,8 +148,8 @@ export class Logger extends EventEmitter { * @param {Object} [opts = {}] - additional options * @return {commands.util.logger.Logger} **/ - debug(style, opts = {}) { - return this._output(style)._fire(Logger.events.debug, opts); + debug(opts = {}) { + return this._output(null, Logger.type.debug)._fire(Logger.events.debug, opts); } /** @@ -128,7 +160,7 @@ export class Logger extends EventEmitter { * @return {commands.util.logger.Logger} **/ warn(opts = {}) { - return this._output(null, Logger.level.warning)._fire(Logger.events.warning, opts); + return this._output(null, Logger.type.warning)._fire(Logger.events.warning, opts); } /** @@ -138,21 +170,44 @@ export class Logger extends EventEmitter { * @param {Object} [opts = {}] - additional options **/ fatal(opts = {}) { - this._output(null, Logger.level.fatal)._fire(Logger.events.fatal, opts); + this._output(null, Logger.type.fatal)._fire(Logger.events.fatal, opts); process.exit(1); } /** - * Logger Levels + * Sets Logger Type + * @public + * @param {String} [level = Logger.type.output] - log's type + * @return {Function} + **/ + level(level = Logger.type.output) { + this._level = level; + return this; + } + + /** + * Logger Types * @static * @type {Object} **/ - static level = { - debug: chalk.green, + static type = { + debug: chalk.white, + output: chalk.green, warning: chalk.yellow, fatal: chalk.red } + /** + * Logger Levels + * @static + * @type {Object} + **/ + static level = { + output: 'logger:output', // Default + debug: 'logger:debug', + silent: 'logger:silent' + } + /** * Logger Events * @static @@ -170,6 +225,11 @@ export class Logger extends EventEmitter { **/ debug: 'commands:util:logger:debug', + /** + * @event output + **/ + output: 'commands:util:logger:output', + /** * @event warning **/ diff --git a/test/specs/config-basic.json b/test/specs/config-basic.json index afa99e1..4096546 100644 --- a/test/specs/config-basic.json +++ b/test/specs/config-basic.json @@ -19,5 +19,6 @@ }, { "destination": "./dist/amd", "format": "amd" - }] + }], + "logLevel": "debug" } From cac44f1567eca64ded126c8da586598a7c4ae182 Mon Sep 17 00:00:00 2001 From: kuakman <3dimentionar@gmail.com> Date: Sun, 5 Mar 2017 17:23:24 -0800 Subject: [PATCH 14/39] util: checkpoint - Quick fixes on commands.util.logger.Logger#_validate() method. --- src/commands/util/logger/logger.es6 | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/commands/util/logger/logger.es6 b/src/commands/util/logger/logger.es6 index 6037223..75c9ba1 100644 --- a/src/commands/util/logger/logger.es6 +++ b/src/commands/util/logger/logger.es6 @@ -74,10 +74,9 @@ export class Logger extends EventEmitter { **/ _validate(type) { const { fatal, debug } = Logger.type; - const { silent, output, debug } = Logger.level; - if(this._level === silent && _.isEqual(type, fatal)) return true; - if(this._level === output && !_.isEqual(type, debug) return true; - if(this._level === debug) return true; + if(this._level === Logger.level.silent && _.isEqual(type, fatal)) return true; + if(this._level === Logger.level.output && !_.isEqual(type, debug)) return true; + if(this._level === Logger.level.debug) return true; return false; } From 603f0dbaecd10e6e7be8d021e1ca87e3e8879e40 Mon Sep 17 00:00:00 2001 From: kuakman <3dimentionar@gmail.com> Date: Mon, 6 Mar 2017 10:05:12 -0800 Subject: [PATCH 15/39] util: checkpoint - Added implementation for logging out to standard out (commands.util.logger.Logger). Added 100% unit test coverage to it. --- src/commands/util/logger/logger.es6 | 111 +++++++++---- test/commands/util/logger/logger.spec.es6 | 180 +++++++++++++++++++++- 2 files changed, 258 insertions(+), 33 deletions(-) diff --git a/src/commands/util/logger/logger.es6 b/src/commands/util/logger/logger.es6 index 75c9ba1..dfc582e 100644 --- a/src/commands/util/logger/logger.es6 +++ b/src/commands/util/logger/logger.es6 @@ -10,7 +10,6 @@ import chalk from 'chalk'; /** * Class Logger -* TODO: Added support for debug | silent | output * @extends events.EventEmitter **/ export class Logger extends EventEmitter { @@ -39,16 +38,53 @@ export class Logger extends EventEmitter { /** * Proxy's 'get' trap override * @public - * @param {Any} target - logger class reference + * @param {commands.util.logger.Logger} target - logger instance reference * @param {String} property - property to resolve * @return {commands.util.logger.Logger} **/ get(target, property) { - if(_.defined(this[property])) return _.bind(this[property], this); + if(_.defined(this[property])) return this[property]; if(_.defined(chalk[property])) return chalk[property]; return null; } + /** + * Proxy's 'set' trap override + * @public + * @param {commands.util.logger.Logger} target - logger instance reference + * @param {String} property - property to resolve + * @param {Any} value - value to set + * @return {Boolean} + **/ + set(target, property, value) { + this[property] = value; + return true; + } + + /** + * Proxy's 'defineProperty' trap override + * @public + * @param {commands.util.logger.Logger} target - logger instance reference + * @param {String} property - property to define + * @return {Boolean} + **/ + defineProperty(target, property) { + this[property] = null; + return true; + } + + /** + * Proxy's 'deleteProperty' trap override + * @public + * @param {commands.util.logger.Logger} target - logger instance reference + * @param {String} property - property to delete + * @return {Boolean} + **/ + deleteProperty(target, property) { + delete this[property]; + return true; + } + /** * Proxy's 'apply' trap override * @private @@ -69,13 +105,13 @@ export class Logger extends EventEmitter { * - If level equals `output` -> will output warn, fatal and output (except debug). * - If level equals `debug` -> will print all logs (warn, fatal, output and debug). * @public - * @param {Function} type - log's type + * @param {Object} type - log's type * @return {Boolean} **/ _validate(type) { const { fatal, debug } = Logger.type; - if(this._level === Logger.level.silent && _.isEqual(type, fatal)) return true; - if(this._level === Logger.level.output && !_.isEqual(type, debug)) return true; + if(this._level === Logger.level.silent && _.isEqual(type.name, fatal.name)) return true; + if(this._level === Logger.level.output && !_.isEqual(type.name, debug.name)) return true; if(this._level === Logger.level.debug) return true; return false; } @@ -83,11 +119,10 @@ export class Logger extends EventEmitter { /** * Appends Message to the internal buffer * @public - * @param {String} [message = ''] - message to add - * @param {String} [style = chalk.white] - message style override + * @param {String} message - message to add * @return {commands.util.logger.Logger} **/ - _add(message = '') { + _add(message) { this._buffer.push(message); return this; } @@ -107,7 +142,7 @@ export class Logger extends EventEmitter { * Fires Event * @public * @param {String} name - event name - * @param [...args] {Any} - arguments to pass to the event being dispatch + * @param opts {Object} - additional options * @return {commands.util.logger.Logger} **/ _fire(name, opts) { @@ -119,14 +154,24 @@ export class Logger extends EventEmitter { * Outputs message to the stdout * @private * @param {Function} style - chalk style override - * @param {String} [level = Logger.level.debug] - level + * @param {Object} type - Logger type * @return {commands.util.logger.Logger} **/ - _output(style, type = Logger.type.debug) { + _output(style, type) { if(!this._validate(type)) return this._flush(); - level = _.defined(style) ? style : level; - console.log(level(this._buffer.join('\n'))); - return this._flush(); + let format = _.defined(style) ? style : type.style; + return this._stdout(format(this._buffer.join('\n')))._flush(); + } + + /** + * Standard Out (console.log wrapper) + * @private + * @param {String} content - content to output in stdout + * @return {commands.util.logger.Logger} + **/ + _stdout(content) { + console.warn(content); + return this; } /** @@ -134,9 +179,10 @@ export class Logger extends EventEmitter { * @public * @emits {Logger.events.debug} * @param {Object} [opts = {}] - additional options + * @param {Object} style - chalk optional style * @return {commands.util.logger.Logger} **/ - out(style, opts = {}) { + out(opts = {}, style) { return this._output(style, Logger.type.output)._fire(Logger.events.output, opts); } @@ -145,10 +191,11 @@ export class Logger extends EventEmitter { * @public * @emits {Logger.events.debug} * @param {Object} [opts = {}] - additional options + * @param {Object} style - chalk optional style * @return {commands.util.logger.Logger} **/ - debug(opts = {}) { - return this._output(null, Logger.type.debug)._fire(Logger.events.debug, opts); + debug(opts = {}, style) { + return this._output(style, Logger.type.debug)._fire(Logger.events.debug, opts); } /** @@ -174,13 +221,13 @@ export class Logger extends EventEmitter { } /** - * Sets Logger Type + * Sets Logger Level * @public - * @param {String} [level = Logger.type.output] - log's type + * @param {String} [level = Logger.level.output] - log's level * @return {Function} **/ - level(level = Logger.type.output) { - this._level = level; + level(lvl = Logger.level.output) { + this._level = lvl; return this; } @@ -190,10 +237,22 @@ export class Logger extends EventEmitter { * @type {Object} **/ static type = { - debug: chalk.white, - output: chalk.green, - warning: chalk.yellow, - fatal: chalk.red + debug: { + name: 'debug', + style: chalk.white + }, + output: { + name: 'output', + style: chalk.green + }, + warning: { + name: 'warning', + style: chalk.yellow + }, + fatal: { + name: 'fatal', + style: chalk.red + } } /** diff --git a/test/commands/util/logger/logger.spec.es6 b/test/commands/util/logger/logger.spec.es6 index a06a284..caffeae 100644 --- a/test/commands/util/logger/logger.spec.es6 +++ b/test/commands/util/logger/logger.spec.es6 @@ -11,19 +11,16 @@ describe('commands.util.logger.Logger', function() { }); beforeEach(() => { - this.mockStatic = this.sandbox.mock(Logger); this.mockProto = this.sandbox.mock(logger); this.mockProcess = this.sandbox.mock(process); }); afterEach(() => { - this.mockStatic.verify(); this.mockProto.verify(); this.mockProcess.verify(); this.sandbox.restore(); - delete this.mockStatic; delete this.mockProto; delete this.mockProcess; }); @@ -39,26 +36,195 @@ describe('commands.util.logger.Logger', function() { }); it('Should adds to the buffer by using constructor function', () => { - logger('hello')('world').debug(logger.magenta); + const expOut = this.mockProto.expects('_stdout') + .once() + .withArgs(sinon.match.string) + .returns(logger); + + logger('hello')('world').out(logger.magenta); + }); + + }); + + describe('_stdout()', () => { + + it('Should use console.warn to output', () => { + // ISOLATED: To avoid dangerous issues with unit tests (stubbing `console.warn`) + const stubConsoleWarn = this.sandbox.stub(console, 'warn', () => {}); + assert.instanceOf(logger._stdout('Hello'), Logger); + stubConsoleWarn.restore(); + }); + + }); + + describe('_validate()', () => { + + it('Should validate to true, if level = silent and type is fatal', () => { + logger.level(Logger.level.silent); + assert.isTrue(logger._validate(Logger.type.fatal)); + }); + + it('Should validate to true, if level = output and type is fatal, warn or output only', () => { + logger.level(Logger.level.output); + assert.isTrue(logger._validate(Logger.type.fatal)); + assert.isTrue(logger._validate(Logger.type.warning)); + assert.isTrue(logger._validate(Logger.type.output)); + assert.isFalse(logger._validate(Logger.type.debug)); + }); + + it('Should validate to true, if level = debug with all types', () => { + logger.level(Logger.level.debug); + assert.isTrue(logger._validate(Logger.type.fatal)); + assert.isTrue(logger._validate(Logger.type.warning)); + assert.isTrue(logger._validate(Logger.type.output)); + assert.isTrue(logger._validate(Logger.type.debug)); + }); + + it('Should validate to false, if level = silent with type is other than fatal', () => { + logger.level(Logger.level.silent); + assert.isFalse(logger._validate(Logger.type.warning)); + assert.isFalse(logger._validate(Logger.type.output)); + assert.isFalse(logger._validate(Logger.type.debug)); + }); + + }); + + describe('out()', () => { + + it('Should write to standard output (out - with event)', () => { + logger.level(Logger.level.output); + const expOut = this.mockProto.expects('_stdout') + .once() + .withArgs(sinon.match.string) + .returns(logger); + + const expEmit = this.mockProto.expects('emit') + .once() + .withArgs(Logger.events.output, logger); + + assert.instanceOf(logger('hello').out(), Logger); + }); + + it('Should write to standard out (out - without event)', () => { + logger.level(Logger.level.output); + const expOut = this.mockProto.expects('_stdout') + .once() + .withArgs(sinon.match.string) + .returns(logger); + + const expEmit = this.mockProto.expects('emit').never(); + + assert.instanceOf(logger('hello').out({ silent: true }, logger.magenta), Logger); + }); + + it('Should NOT write to standard out (due to validation)', () => { + logger.level(Logger.level.silent); + const expOut = this.mockProto.expects('_stdout').never(); + + const expEmit = this.mockProto.expects('emit') + .once() + .withArgs(Logger.events.output, logger); + + assert.instanceOf(logger('Not Logged').out(), Logger); }); }); describe('debug()', () => { - xit('Should debug', () => {}); + it('Should write to standard output (debug - with event)', () => { + logger.level(Logger.level.debug); + const expOut = this.mockProto.expects('_stdout') + .once() + .withArgs(sinon.match.string) + .returns(logger); + + const expEmit = this.mockProto.expects('emit') + .once() + .withArgs(Logger.events.debug, logger); + + assert.instanceOf(logger('hello').debug(), Logger); + }); + + it('Should write to standard out (debug - without event)', () => { + logger.level(Logger.level.debug); + const expOut = this.mockProto.expects('_stdout') + .once() + .withArgs(sinon.match.string) + .returns(logger); + + const expEmit = this.mockProto.expects('emit').never(); + + assert.instanceOf(logger('hello').debug({ silent: true }), Logger); + }); }); describe('warn()', () => { - xit('Should warn', () => {}); + it('Should write to standard output (warning - with event)', () => { + logger.level(Logger.level.output); + const expOut = this.mockProto.expects('_stdout') + .once() + .withArgs(sinon.match.string) + .returns(logger); + + const expEmit = this.mockProto.expects('emit') + .once() + .withArgs(Logger.events.warning, logger); + + assert.instanceOf(logger('hello').warn(), Logger); + }); + + it('Should write to standard out (warning - without event)', () => { + logger.level(Logger.level.output); + const expOut = this.mockProto.expects('_stdout') + .once() + .withArgs(sinon.match.string) + .returns(logger); + + const expEmit = this.mockProto.expects('emit').never(); + + assert.instanceOf(logger('hello').warn({ silent: true }), Logger); + }); }); describe('fatal()', () => { - xit('Should fatal', () => {}); + it('Should write to standard output (fatal - with event) and process.exit(1)', () => { + logger.level(Logger.level.output); + const expOut = this.mockProto.expects('_stdout') + .once() + .withArgs(sinon.match.string) + .returns(logger); + + const expExit = this.mockProcess.expects('exit') + .once() + .withArgs(1); + + const expEmit = this.mockProto.expects('emit') + .once() + .withArgs(Logger.events.fatal, logger); + + assert.isUndefined(logger('hello').fatal()); + }); + + it('Should write to standard out (fatal - without event) and process.exit(1)', () => { + logger.level(Logger.level.output); + const expOut = this.mockProto.expects('_stdout') + .once() + .withArgs(sinon.match.string) + .returns(logger); + + const expExit = this.mockProcess.expects('exit') + .once() + .withArgs(1); + + const expEmit = this.mockProto.expects('emit').never(); + + assert.isUndefined(logger('hello').fatal({ silent: true })); + }); }); From 2c19d75efa6326ab64ae53261c2fca487e12aa7b Mon Sep 17 00:00:00 2001 From: kuakman <3dimentionar@gmail.com> Date: Tue, 7 Mar 2017 23:16:24 -0800 Subject: [PATCH 16/39] util: checkpoint - Added commands.util.commander.Commander. Updated README.md. Clean up. --- CHANGELOG.md | 11 -- README.md | 34 ++++- src/build/build.es6 | 21 +--- src/commands/bin/sqbox.es6 | 76 ++++++----- src/commands/command.es6 | 46 +++---- src/commands/util/adt/iterator.es6 | 4 +- src/commands/util/commander/commander.es6 | 118 ++++++++++++++++++ src/commands/util/exception/adt/queue.es6 | 4 +- src/commands/util/exception/adt/stack.es6 | 0 .../util/exception/command/command.es6 | 4 +- src/commands/util/exception/exception.es6 | 4 +- .../util/exception/proxy/interface.es6 | 4 +- 12 files changed, 232 insertions(+), 94 deletions(-) create mode 100644 src/commands/util/commander/commander.es6 delete mode 100644 src/commands/util/exception/adt/stack.es6 diff --git a/CHANGELOG.md b/CHANGELOG.md index ac35c19..efe4ef1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,12 +1 @@ ## Rinoa v1.0.0 - ROADMAP - -##### Features: -* [ ] Esprima ES6 AST Parsing - * [ ] Imports / Exports - * [ ] Annotations -* [ ] Output formats - * [ ] UMD (Global, AMD, CommonJS) - * [ ] AMD - * [ ] CommonJS - * [ ] ES -* [ ] Multiple Bundles diff --git a/README.md b/README.md index f16e788..f758e2c 100644 --- a/README.md +++ b/README.md @@ -9,24 +9,52 @@ #### Introduction +Experimental ES6/CommonJS/AMD Module Bundler + +#### Installation + +```npm install [-g] squarebox``` + +#### Usage + +CLI Commands + ``` -TODO +global option: -c (sqbox.js|.sqboxrc|URL) | Default: ./.sqboxrc +contextual help - sqbox [command] help + +* sqbox help - global help (list of commands and general usage) +* sbox bundle --options +* sbox clean +* sbox visualize ``` -#### Installation +Programmatic API + +``` +const sqbox = require('squarebox'); +sqbox.clean([opts]) + .bundle([config]) + .visualize([opts]); +``` + +#### Official Documentation ``` TODO ``` -#### Usage +#### Configuration + ``` TODO ``` #### Contribute + ``` TODO ``` + [![forthebadge](http://forthebadge.com/images/badges/built-with-love.svg)](http://nahuel.io) by [Nahuel IO](http://nahuel.io) diff --git a/src/build/build.es6 b/src/build/build.es6 index a8613cc..3e18e44 100644 --- a/src/build/build.es6 +++ b/src/build/build.es6 @@ -3,14 +3,14 @@ * @author Patricio Ferreira <3dimentionar@gmail.com> **/ import Package from '../../../package.json'; -import Command from '../commands/command'; +import Command from 'commands/command'; import _ from 'underscore'; import extend from 'extend'; import yargs from 'yargs'; /** * Class Build -* @extends command.Command +* @extends {command.Command} **/ class Build extends Command { @@ -18,30 +18,19 @@ class Build extends Command { * Build Run * @public * @override - * @return build.Build + * @return {build.Build} **/ run() { return this; } - /** - * Setup Yargs - * @static - * @override - * @return build.Build - **/ - static setup() { - Command.setup(); - return this; - } - /** * Static Run * @static - * @return build.Build + * @return {build.Build} **/ static run() { - return this.setup().new(this.args()); + return new this(); } } diff --git a/src/commands/bin/sqbox.es6 b/src/commands/bin/sqbox.es6 index 7761fd5..f147d04 100644 --- a/src/commands/bin/sqbox.es6 +++ b/src/commands/bin/sqbox.es6 @@ -2,62 +2,74 @@ * @module commands.bin * @author Patricio Ferreira <3dimentionar@gmail.com> **/ -import Command from 'commands/command'; +import './util/mixins'; import extend from 'extend'; -import yargs from 'yargs'; +import Command from 'commands/command'; +import Commander from 'util/commander/commander'; + +let enforcer = new Symbol(); /** * Class SquareBox -* @extends commands.Command +* @extends {commands.Command} +* +* @uses {util.commander.Commander} **/ class SquareBox extends Command { /** - * Command Defaults - * @static - * @type {Object} + * Constructor + * @public + * @param {Symbol} pte - constructor enforcer + * @param [args = {}] {Object} Constructor arguments + * @return {commands.bin.SquareBox} **/ - static defaults = extend(true, Command.defaults, { - 'config': '.squarebox.json', - 'source-scan': './src', - 'source-extensions': ['.js', '.es6', '.es'], - 'source-alias': , - 'target-destination': './dist', - 'target-format': 'ifie' - }); + constructor(pte, ...args) { + super(); + return this.validate().register().settings(...args); + } /** - * Command options - * @static - * @type {Array} + * Constructor Validation + * @public + * @throws {Error} Private violation + * @param {Symbol} pte - constructor enforcer + * @return {commands.bin.SquareBox} **/ - static options = Command.options.concat([ - 'config', - 'source-scan', - 'source-extensions' - 'source-alias', - 'target-destination', - 'target-format' - ]); + validate(pte) { + if(!_.isEqual(pte, enforcer)) throw new Error('Private Violation'); + return this; + } /** - * Setup Yargs - * @static - * @override - * @return + * Register Command Factories + * @public + * @return {commands.bin.SquareBox} **/ - static setup() { - Command.setup(); + register() { + // TODO: Factory return this; } + /** + * Available Commands + * @static + * @type {Array} + **/ + static commands = [ + 'help', + 'clean', + 'bundle', + 'visualize' + ]; + /** * Static Run * @static * @return commands.bin.SquareBox **/ static run() { - return this.setup().new(this.args()); + return new Proxy(this.new(enforcer, { cwd: process.cwd() }), Commander.new()); } } diff --git a/src/commands/command.es6 b/src/commands/command.es6 index f69217d..1b41b1a 100644 --- a/src/commands/command.es6 +++ b/src/commands/command.es6 @@ -3,10 +3,8 @@ * @author Patricio Ferreira <3dimentionar@gmail.com> **/ import { EventEmitter } from 'events'; -import './util/mixins'; -import _ from 'underscore'; +import _ from './util/mixins'; import extend from 'extend'; -import yargs from 'yargs'; import JSON from './util/proxy/json'; import Collection from './util/adt/collection'; import CommandException from './util/exception/command/command'; @@ -16,19 +14,28 @@ import CommandException from './util/exception/command/command'; * @extends {events.EventEmitter} * * @uses {commands.util.proxy.JSON} -* @uses {commands.util.proxy.Asynchronous} **/ -export default class Command extends EventEmitter { +class Command extends EventEmitter { /** * Constructor * @public - * @param [args = {}] {Object} Constructor arguments + * @param {Object} [args = {}] - constructor arguments * @return {commands.Command} **/ - constructor(args) { + constructor(args = {}) { super(); - return JSON.proxy(extend(true, this, this.defaults, _.pick(args, this.constructor.options))); + return JSON.proxy(this.settings(args)); + } + + /** + * Set settings + * @public + * @param {Object} [options = {}] - command options + * @return {commands.Command} + **/ + settings(options) { + return extend(true, this, Command.defaults, _.pick(options, this.constructor.options)); } /** @@ -135,15 +142,6 @@ export default class Command extends EventEmitter { return this.target().format; } - /** - * Retrieves Yargs Arguments - * @public - * @return {Object} - **/ - args() { - return yargs.argv; - } - /** * Command Defaults * @static @@ -159,7 +157,8 @@ export default class Command extends EventEmitter { * @type {Array} **/ static options = [ - 'env' + 'env', + 'cwd' ]; /** @@ -172,15 +171,6 @@ export default class Command extends EventEmitter { done: 'commands:command:done' }; - /** - * Default Yargs setup - * @static - * @return {commands.Command} - **/ - static setup() { - return this; - } - /** * Static Constructor * @static @@ -192,3 +182,5 @@ export default class Command extends EventEmitter { } } + +export default Command; diff --git a/src/commands/util/adt/iterator.es6 b/src/commands/util/adt/iterator.es6 index 8855ccf..6b777f2 100644 --- a/src/commands/util/adt/iterator.es6 +++ b/src/commands/util/adt/iterator.es6 @@ -9,7 +9,7 @@ import _ from 'underscore'; * Class Iterator * @extends events.EventEmitter **/ -export default class Iterator extends EventEmitter { +class Iterator extends EventEmitter { /** * Internal collection @@ -130,3 +130,5 @@ export default class Iterator extends EventEmitter { } } + +export default Iterator; diff --git a/src/commands/util/commander/commander.es6 b/src/commands/util/commander/commander.es6 new file mode 100644 index 0000000..ad8ef11 --- /dev/null +++ b/src/commands/util/commander/commander.es6 @@ -0,0 +1,118 @@ +/** +* @module commands.util.commander +* @author Patricio Ferreira <3dimentionar@gmail.com> +**/ +import { EventEmitter } from 'events'; +import _ from 'underscore'; +import extend from 'extend'; +import yargs from 'yargs'; + +/** +* Class Commander +* @extends {events.Emitter} +**/ +class Commander extends EventEmitter { + + /** + * Constructor + * @public + * @param [...args] {Any} - additional arguments + * @return {commands.util.commander.Commander} + **/ + constructor(...args) { + super(); + return extend(true, this, ...args); + } + + /** + * Default Proxy getPropertyOf trap + * @public + * @param {commands.Command} target - command reference + * @return {Object} + **/ + getPropertyOf(target) { + return target.constructor.protoype; + } + + /** + * Default Proxy get trap + * @public + * @param {commands.Command} target - command reference + * @param {String} property - command reference + * @return {Any} + **/ + get(target, property) { + // TODO + return target[property]; + } + + /** + * Default Proxy get trap + * @public + * @param {commands.Command} target - command reference + * @param {String} property - property name + * @param {Any} value - value to set + * @return {Any} + **/ + set(target, property, value) { + return (target[property] = value); + } + + /** + * Default arguments parsing + * @public + * @return {commands.util.commander.Commander} + **/ + async parse() { + // TODO: [js,json,uri] + return this; + } + + /** + * Available Artifacts + * @static + * @type {Array} + **/ + static artifacts = []; // TODO + + /** + * Commander Defaults + * @static + * @type {Object} + **/ + static defaults = { + 'config': '.sqboxrc', + 'source-scan': '.', + 'source-extensions': ['.js', '.jsx', '.es6', '.es'], + 'source-alias': {}, + 'target-destination': './dist', + 'target-format': 'ifie' + }; + + /** + * Commander options + * @static + * @type {Array} + **/ + static options = [ + 'config', + 'source-scan', + 'source-extensions' + 'source-alias', + 'target-destination', + 'target-format' + ]; + + /** + * Static Constructor + * @static + * @param [...args] {Any} constructor arguments + * @return {commands.util.command.Commander} + **/ + static new(...args) { + return new this(...args); + } + +} + +export default Commander.new(); diff --git a/src/commands/util/exception/adt/queue.es6 b/src/commands/util/exception/adt/queue.es6 index 83d63c3..8450778 100644 --- a/src/commands/util/exception/adt/queue.es6 +++ b/src/commands/util/exception/adt/queue.es6 @@ -10,7 +10,7 @@ import Exception from '../exception'; * Class QueueException * @extends {commands.util.exception.Exception} **/ -export default class QueueException extends Exception { +class QueueException extends Exception { /** * Constructor @@ -35,3 +35,5 @@ export default class QueueException extends Exception { }); } + +export default QueueException; diff --git a/src/commands/util/exception/adt/stack.es6 b/src/commands/util/exception/adt/stack.es6 deleted file mode 100644 index e69de29..0000000 diff --git a/src/commands/util/exception/command/command.es6 b/src/commands/util/exception/command/command.es6 index bd20a4b..89e7c10 100644 --- a/src/commands/util/exception/command/command.es6 +++ b/src/commands/util/exception/command/command.es6 @@ -10,7 +10,7 @@ import Exception from '../exception'; * Class CommandException * @extends {commands.util.exception.Exception} **/ -export default class CommandException extends Exception { +class CommandException extends Exception { /** * Constructor @@ -35,3 +35,5 @@ export default class CommandException extends Exception { }); } + +export default CommandException; diff --git a/src/commands/util/exception/exception.es6 b/src/commands/util/exception/exception.es6 index 2471213..0664a7a 100644 --- a/src/commands/util/exception/exception.es6 +++ b/src/commands/util/exception/exception.es6 @@ -10,7 +10,7 @@ import Logger from '../logger/logger'; * Class Exception * @extends {Error} **/ -export default class Exception extends Error { +class Exception extends Error { /** * Constructor @@ -48,3 +48,5 @@ export default class Exception extends Error { } } + +export default Exception; diff --git a/src/commands/util/exception/proxy/interface.es6 b/src/commands/util/exception/proxy/interface.es6 index a91030a..fe4821c 100644 --- a/src/commands/util/exception/proxy/interface.es6 +++ b/src/commands/util/exception/proxy/interface.es6 @@ -10,7 +10,7 @@ import Exception from '../exception'; * Class InterfaceException * @extends {commands.util.exception.Exception} **/ -export default class InterfaceException extends Exception { +class InterfaceException extends Exception { /** * Constructor @@ -36,3 +36,5 @@ export default class InterfaceException extends Exception { }); } + +export default InterfaceException; From fa220c8f91133d38c7439b5acdd778e6e73472cb Mon Sep 17 00:00:00 2001 From: kuakman <3dimentionar@gmail.com> Date: Thu, 9 Mar 2017 11:00:01 -0800 Subject: [PATCH 17/39] util: checkpoint - Added unit test coverage for commands.util.factory.Factory. Added initial implementation for commands.util.visitor.Visitor and Visited. --- src/commands/bin/sqbox.es6 | 8 +- src/commands/bin/visitor/commander.es6 | 75 +++++ src/commands/util/commander/commander.es6 | 118 -------- src/commands/util/factory/factory.es6 | 189 ++++++++++++ src/commands/util/visitor/visited.es6 | 72 +++++ src/commands/util/visitor/visitor.es6 | 75 +++++ test/commands/util/factory/factory.spec.es6 | 306 ++++++++++++++++++++ 7 files changed, 722 insertions(+), 121 deletions(-) create mode 100644 src/commands/bin/visitor/commander.es6 delete mode 100644 src/commands/util/commander/commander.es6 create mode 100644 src/commands/util/factory/factory.es6 create mode 100644 src/commands/util/visitor/visited.es6 create mode 100644 src/commands/util/visitor/visitor.es6 create mode 100644 test/commands/util/factory/factory.spec.es6 diff --git a/src/commands/bin/sqbox.es6 b/src/commands/bin/sqbox.es6 index f147d04..62a0829 100644 --- a/src/commands/bin/sqbox.es6 +++ b/src/commands/bin/sqbox.es6 @@ -2,10 +2,12 @@ * @module commands.bin * @author Patricio Ferreira <3dimentionar@gmail.com> **/ -import './util/mixins'; +import 'commands/util/mixins'; import extend from 'extend'; + +import Factory from 'commands/util/factory/factory'; import Command from 'commands/command'; -import Commander from 'util/commander/commander'; +import Commander from 'commands/util/commander/commander'; let enforcer = new Symbol(); @@ -47,7 +49,7 @@ class SquareBox extends Command { * @return {commands.bin.SquareBox} **/ register() { - // TODO: Factory + Factory.registerAll(Squarebox.commands); return this; } diff --git a/src/commands/bin/visitor/commander.es6 b/src/commands/bin/visitor/commander.es6 new file mode 100644 index 0000000..34a211a --- /dev/null +++ b/src/commands/bin/visitor/commander.es6 @@ -0,0 +1,75 @@ +/** +* @module commands.util.visitor +* @author Patricio Ferreira <3dimentionar@gmail.com> +**/ +import _ from 'underscore'; +import extend from 'extend'; +import yargs from 'yargs'; +import Visitor from 'commands/util/visitor/visitor'; +import QueueAsync from 'commands/util/adt/queue-async'; + +/** +* Class Commander +* @extends {commands.util.visitor.Visitor} +**/ +class Commander extends Visitor { + + /** + * Default arguments parsing + * @public + * @return {commands.bin.visitor.Commander} + **/ + async parse() { + // TODO: [js,json,uri] QueueAsync for executing for configuration + return this; + } + + /** + * Visitor Name + * @public + * @type {String} + **/ + get name() { + return 'CommanderVisitor'; + } + + /** + * Available Artifacts + * @static + * @type {Array} + **/ + static artifacts = [ + 'configuration' + ]; // TODO + + /** + * Commander Defaults + * @static + * @type {Object} + **/ + static defaults = { + 'config': '.sqboxrc', + 'source-scan': '.', + 'source-extensions': ['.js', '.jsx', '.es6', '.es'], + 'source-alias': {}, + 'target-destination': './dist', + 'target-format': 'ifie' + }; + + /** + * Commander options + * @static + * @type {Array} + **/ + static options = [ + 'config', + 'source-scan', + 'source-extensions' + 'source-alias', + 'target-destination', + 'target-format' + ]; + +} + +export default Commander; diff --git a/src/commands/util/commander/commander.es6 b/src/commands/util/commander/commander.es6 deleted file mode 100644 index ad8ef11..0000000 --- a/src/commands/util/commander/commander.es6 +++ /dev/null @@ -1,118 +0,0 @@ -/** -* @module commands.util.commander -* @author Patricio Ferreira <3dimentionar@gmail.com> -**/ -import { EventEmitter } from 'events'; -import _ from 'underscore'; -import extend from 'extend'; -import yargs from 'yargs'; - -/** -* Class Commander -* @extends {events.Emitter} -**/ -class Commander extends EventEmitter { - - /** - * Constructor - * @public - * @param [...args] {Any} - additional arguments - * @return {commands.util.commander.Commander} - **/ - constructor(...args) { - super(); - return extend(true, this, ...args); - } - - /** - * Default Proxy getPropertyOf trap - * @public - * @param {commands.Command} target - command reference - * @return {Object} - **/ - getPropertyOf(target) { - return target.constructor.protoype; - } - - /** - * Default Proxy get trap - * @public - * @param {commands.Command} target - command reference - * @param {String} property - command reference - * @return {Any} - **/ - get(target, property) { - // TODO - return target[property]; - } - - /** - * Default Proxy get trap - * @public - * @param {commands.Command} target - command reference - * @param {String} property - property name - * @param {Any} value - value to set - * @return {Any} - **/ - set(target, property, value) { - return (target[property] = value); - } - - /** - * Default arguments parsing - * @public - * @return {commands.util.commander.Commander} - **/ - async parse() { - // TODO: [js,json,uri] - return this; - } - - /** - * Available Artifacts - * @static - * @type {Array} - **/ - static artifacts = []; // TODO - - /** - * Commander Defaults - * @static - * @type {Object} - **/ - static defaults = { - 'config': '.sqboxrc', - 'source-scan': '.', - 'source-extensions': ['.js', '.jsx', '.es6', '.es'], - 'source-alias': {}, - 'target-destination': './dist', - 'target-format': 'ifie' - }; - - /** - * Commander options - * @static - * @type {Array} - **/ - static options = [ - 'config', - 'source-scan', - 'source-extensions' - 'source-alias', - 'target-destination', - 'target-format' - ]; - - /** - * Static Constructor - * @static - * @param [...args] {Any} constructor arguments - * @return {commands.util.command.Commander} - **/ - static new(...args) { - return new this(...args); - } - -} - -export default Commander.new(); diff --git a/src/commands/util/factory/factory.es6 b/src/commands/util/factory/factory.es6 new file mode 100644 index 0000000..c225234 --- /dev/null +++ b/src/commands/util/factory/factory.es6 @@ -0,0 +1,189 @@ +/** +* @module commands.util.factory +* @author Patricio Ferreira <3dimentionar@gmail.com> +**/ +import _ from 'commands/util/mixins'; +import fs from 'fs-extra'; +import path, { resolve } from 'path'; +import extend from 'extend'; +import { EventEmitter } from 'events'; +import logger from 'commands/util/logger/logger'; + +/** +* Class Factory +* @extends {events.EventEmitter} +**/ +class Factory extends EventEmitter { + + /** + * Constructor + * @public + * @param {Object} [attrs = {}] - constructor attributes + * @return {commands.util.factory.Factory} + **/ + constructor(attrs = {}) { + super(); + return extend(true, this, attrs, { _factories: new Map() }).basePath(); + } + + /** + * Sets a base path + * @public + * @param {String} [ph = Factory.basePath] - base path to resolve factories filepaths + * @return {commands.util.factory.Factory} + **/ + basePath(ph = Factory.basePath) { + this.path = ph; + return this; + } + + /** + * Resolves path to factory class file + * @private + * @param {String} ph - path to resolve + * @return {String} + **/ + _resolve(ph) { + return resolve(this.path, ph); + } + + /** + * Validates factory path to file + * @private + * @param {String} ph - path to registered factory + * @return {Boolean} + **/ + _validate(ph) { + try { + return (_.defined(ph) && _.isString(ph) && ph.length > 0 && _.defined(require.resolve(this._resolve(ph)))); + } catch(ex) { + logger(`${this.toString()} > [ERROR-CODE] ${ex.code}`).out({}, logger.white); + logger(ex.message).warn(logger); + return false; + } + } + + /** + * Resolves which factory class constructor to use for instanciation + * If a static new method is detected on the factory, this will be used. + * If no static new method is detected and the constructor is a function, operator new will be used. + * Finally, if the factory is not a Function, this method will return it verbatim (no arguments will be passed). + * @private + * @param {Any} o - factory object + * @param {Any} [...args] - constructor factory arguments + * @return {Any} + **/ + _new(o, ...args) { + if(_.isFunction(o)) return _.defined(o.new) ? o.new(...args) : new o(...args); + return o; + } + + /** + * Perform a look up and retrieves the first factory matching the path. Returns null otherwise + * @public + * @param {String} [ph = ''] - path to registered factory + * @return {Any} + **/ + find(ph = '') { + for(let [k, v] of this._factories.entries()) + if(k === ph) return v; + return null; + } + + /** + * Returns true if the given path was registered previously, false otherwise + * @public + * @param {String} ph - path to registered factory + * @return {Boolean} + **/ + exists(ph) { + if(!_.defined(ph)) return false; + return this._factories.has(ph); + } + + /** + * Registers a single factory object with a given path, if it haven't been registered before. + * @public + * @param {String} ph - factory path location + * @return {commands.util.factory.Factory} + **/ + register(ph) { + if(this._validate(ph) && !this.exists(ph)) this._factories.set(ph, this._resolve(ph)); + return this; + } + + /** + * Registers a list of factory objects with a given path list, if they haven't been registered before. + * @public + * @param {Array} [paths = []] - factory path locations + * @return {commands.util.factory.Factory} + **/ + registerAll(paths = []) { + _.each(paths, this.register, this); + return this; + } + + /** + * Unregisters a single factory object with a given path, if it have been registered before. + * @public + * @param {String} ph - factory path location + * @return {commands.util.factory.Factory} + **/ + unregister(ph) { + if(this.exists(ph)) this._factories.delete(ph); + return this; + } + + /** + * Unregisters a list of factory objects with a given path list, if they have been registered before. + * @public + * @param {Array} [paths = []] - factory path locations + * @return {commands.util.factory.Factory} + **/ + unregisterAll(paths = []) { + _.each(paths, this.unregister, this); + return this; + } + + /** + * Obtain a new instance of the factory, by optionally passing arguments to the constructor. + * If the factory is not found, returns null. + * @public + * @param {String} ph - registred factory path + * @param {Any} [...args] - factory constructor arguments + * @return {Any} + **/ + get(ph, ...args) { + return this.exists(ph) ? this._new(require(this.find(ph)).default, ...args) : null; + } + + /** + * String representation of this class + * @public + * @override + * @return {String} + **/ + toString() { + return this.constructor.name; + } + + /** + * Factory Defaults + * @static + * @type {Object} + **/ + static basePath = process.cwd(); + + /** + * Static Constructor + * @static + * @param {Any} [...args] - constructor arguments + * @return {commands.util.factory.Factory} + **/ + static new(...args) { + return new this(...args); + } + +} + +export default Factory.new(); diff --git a/src/commands/util/visitor/visited.es6 b/src/commands/util/visitor/visited.es6 new file mode 100644 index 0000000..558eb54 --- /dev/null +++ b/src/commands/util/visitor/visited.es6 @@ -0,0 +1,72 @@ +/** +* @module commands.util.visitor +* @author Patricio Ferreira <3dimentionar@gmail.com> +**/ +import { EventEmitter } from 'events'; +import _ from 'underscore'; +import extend from 'extend'; +import Visitor from 'commands/util/visitor/visitor'; + +/** +* Class Visited +* @extends {events.EventEmitter} +**/ +class Visited extends EventEmitter { + + /** + * Constructor + * @public + * @param {Any} [...args] - constructor arguments + * @return {commands.util.visitor.Visited} + **/ + constructor(...args) { + super(); + return new Proxy(extend(true, this, ...args), this); + } + + /** + * Proxy get trap override + * @public + * @override + * @param {Any} target - visited target reference + * @param {String} property - visited target property + * @param {Any} receiver - visited target constructor + * @return {Any} + **/ + get(target, property, receiver) { + return _.defined(target[property]) ? target[property] : this[property]; + } + + /** + * Returns true if a given visitor is defined and an instance of commands.util.visitor.Visitor, false otherwise + * @public + * @param {commands.util.visitor.Visitor} visitor - visitor to validate + * @return {Boolean} + **/ + validate(visitor) { + return _.defined(visitor) && _.instanceOf(visitor, Visitor); + } + + /** + * Default strategy that accepts visitor by this visited instance + * @public + * @param {commands.util.visitor.Visitor} vi - visitor to accept + * @return {commands.util.visitor.Visited} + **/ + accept(visitor) { + return this.validate(visitor) ? visitor.visit(this) : this; + } + + /** + * Static Constructor + * @static + * @param [...args] {Any} constructor arguments + * @return {commands.util.visitor.Visited} + **/ + static new(...args) { + return new this(...args); + } + +} + +export default Visited; diff --git a/src/commands/util/visitor/visitor.es6 b/src/commands/util/visitor/visitor.es6 new file mode 100644 index 0000000..40d83bd --- /dev/null +++ b/src/commands/util/visitor/visitor.es6 @@ -0,0 +1,75 @@ +/** +* @module commands.util.visitor +* @author Patricio Ferreira <3dimentionar@gmail.com> +**/ +import { EventEmitter } from 'events'; +import _ from 'underscore'; +import extend from 'extend'; +import Visited from 'commands/util/visitor/visited'; +import InterfaceException from 'commands/util/exception/proxy/interface'; + +/** +* Class Visitor +* @extends {events.EventEmitter} +**/ +class Visitor extends EventEmitter { + + /** + * Constructor + * @public + * @param {Any} [...args] - constructor arguments + * @return {commands.util.visitor.Visitor} + **/ + constructor(...args) { + super(); + return extend(true, this, ...args); + } + + /** + * Returns true if the visited instance implements Visited interface. + * Otherwise, this method will raise an InterfaceException. + * @public + * @throws {commands.util.exception.proxy.InterfaceException} + * @param {commands.util.visitor.Visited} vi - visited instance + * @return {Boolean} + **/ + validate(vi) { + if(!_.defined(vi)) return false; + if(!_.instanceOf(vi, Visited)) + throw InterfaceException.new('interface', { name: 'commands.util.visitor.Visited' }); + return true; + } + + /** + * Default Visit Strategy will return the visited object verbatim. + * This method is most likely to be overriden by subsclasses of this visitor. + * @public + * @param {commands.util.visitor.Visited} vi - instance to be visited by this visitor + * @return {commands.util.visitor.Visited} + **/ + visit(vi) { + return this.validate(vi) ? vi : null; + } + + /** + * Visitor Name + * @public + * @type {String} + **/ + get name() { + return 'Visitor'; + } + + /** + * Static Constructor + * @static + * @param [...args] {Any} constructor arguments + * @return {commands.util.visitor.Visitor} + **/ + static new(...args) { + return new this(...args); + } + +} + +export default Visitor; diff --git a/test/commands/util/factory/factory.spec.es6 b/test/commands/util/factory/factory.spec.es6 new file mode 100644 index 0000000..d85e3c9 --- /dev/null +++ b/test/commands/util/factory/factory.spec.es6 @@ -0,0 +1,306 @@ +/** +* @module commands.util.factory +* @author Patricio Ferreira <3dimentionar@gmail.com> +**/ +import factory from 'commands/util/factory/factory'; +import logger from 'commands/util/logger/logger'; +import Command from 'commands/command'; +import Collection from 'commands/util/adt/collection'; + +describe('commands.util.factory.Factory', function() { + + before(() => { + this.sandbox = sinon.sandbox.create(); + }); + + beforeEach(() => { + this.mockLogger = this.sandbox.mock(logger); + this.mockProto = this.sandbox.mock(factory); + }); + + afterEach(() => { + this.mockLogger.verify(); + this.mockProto.verify(); + + this.sandbox.restore(); + + delete this.mockLogger; + delete this.mockProto; + }); + + after(() => { + delete this.sandbox; + }); + + describe('constructor()', () => { + + it('Should not be a constructor', () => { + assert.instanceOf(factory, Object); + assert.isNotNull(factory._factories); + assert.instanceOf(factory._factories, Map); + assert.isNotNull(factory.register); + }); + + }); + + describe('basePath()', () => { + + it('Should set a basePath', () => { + const input = './src/commands'; + assert.typeOf(factory.basePath(input), 'object'); + assert.equal(input, factory.path); + }); + + it('Should be same basePath when importing multiple times (singleton check)', () => { + const singleton = require('commands/util/factory/factory').default; + assert.deepEqual(singleton, factory); + singleton.basePath('./src/commands/util/adt'); + assert.equal(singleton.path, factory.path); + }); + + }); + + describe('_resolve()', () => { + + it('Should resolve a given path with the basepath', () => { + assert.equal(factory._resolve('collection'), path.resolve(factory.path, 'collection')); + }); + + }); + + describe('_validate()', () => { + + it('Should return true: path is valid', () => { + assert.isTrue(factory._validate('collection')); + }); + + it('Should return false: path is not defined', () => { + assert.isFalse(factory._validate()); + }); + + it('Should return false: path is not a string', () => { + assert.isFalse(factory._validate(1)); + }); + + it('Should return false: path is not an empty string', () => { + assert.isFalse(factory._validate('')); + }); + + it('Should return false: resolved path doesn\'t exists (fs.statSync)', () => { + const expLoggerOutput = this.mockLogger.expects('_stdout').atLeast(1).returns(logger); + assert.isFalse(factory._validate('file-unexistent')); + }); + + }); + + describe('_new()', () => { + + it('Should return factory verbatim', () => { + const verbatimFactory = { option: true } + assert.equal(factory._new(verbatimFactory), verbatimFactory); + }); + + it('Should instanciate factory using static constructor', () => { + assert.instanceOf(factory._new(Command), Command); + }); + + it('Should instanciate factory using operator `new`', () => { + const FakeFactory = function() {}; + assert.instanceOf(factory._new(FakeFactory), FakeFactory); + }); + + }); + + describe('register()', () => { + + it('Should register a factory', () => { + const input = 'collection'; + const expValidate = this.mockProto.expects('_validate') + .once() + .withArgs(input) + .returns(true); + + const expExists = this.mockProto.expects('exists') + .once() + .withArgs(input) + .returns(false); + + assert.instanceOf(factory.register(input), Object); + }); + + it('Should NOT register a factory (not valid)', () => { + const input = 'non-existent'; + const expValidate = this.mockProto.expects('_validate') + .once() + .withArgs(input) + .returns(false); + + const expExists = this.mockProto.expects('exists').never(); + + assert.instanceOf(factory.register(input), Object); + assert.equal(1, factory._factories.size); + }); + + it('Should NOT register a factory (already registered)', () => { + const input = 'non-existent'; + const expValidate = this.mockProto.expects('_validate') + .once() + .withArgs(input) + .returns(false); + + const expExists = this.mockProto.expects('exists').never(); + + assert.instanceOf(factory.register(input), Object); + assert.equal(1, factory._factories.size); + }); + + }); + + describe('registerAll()', () => { + + it('Should register a list of factories', () => { + const input = ['stack', 'queue']; + const expValidate = this.mockProto.expects('_validate') + .twice() + .withArgs(sinon.match('stack').or(sinon.match('queue'))) + .returns(true); + + const expExists = this.mockProto.expects('exists') + .twice() + .withArgs(sinon.match('stack').or(sinon.match('queue'))) + .returns(false); + + assert.instanceOf(factory.registerAll(input), Object); + assert.equal(3, factory._factories.size); + }); + + it('Should register some out of a list of factories', () => { + const input = ['stack', 'queue-async']; + const expValidate = this.mockProto.expects('_validate') + .twice() + .withArgs(sinon.match('stack').or(sinon.match('queue'))) + .returns(true); + + const expExists = this.mockProto.expects('exists') + .twice() + .withArgs(sinon.match('stack').or(sinon.match('queue'))); + + expExists.onFirstCall().returns(true); // with stack + expExists.onSecondCall().returns(false); // with queue-async + + assert.instanceOf(factory.registerAll(input), Object); + assert.equal(4, factory._factories.size); + }); + + it('Should NOT register an empty list of factory paths', () => { + assert.instanceOf(factory.registerAll(), Object); + assert.equal(4, factory._factories.size); + }); + + }); + + describe('unregister()', () => { + + it('Should unregister a factory', () => { + const input = 'queue-async'; + const expExists = this.mockProto.expects('exists') + .once() + .withArgs(input) + .returns(true); + + assert.instanceOf(factory.unregister(input), Object); + assert.equal(3, factory._factories.size); + }); + + it('Should NOT unregister a factory (doesn\'t exists)', () => { + const input = 'non-existent'; + const expExists = this.mockProto.expects('exists') + .once() + .withArgs(input) + .returns(false); + + assert.instanceOf(factory.unregister(input), Object); + assert.equal(3, factory._factories.size); + }); + + }); + + describe('unregisterAll()', () => { + + it('Should unregister a list of factories', () => { + const input = ['stack', 'queue']; + const expExists = this.mockProto.expects('exists') + .twice() + .withArgs(sinon.match('stack').or(sinon.match('queue'))) + .returns(true); + + assert.instanceOf(factory.unregisterAll(input), Object); + assert.equal(1, factory._factories.size); + }); + + it('Should unregister some, out of a list of factories', () => { + const input = ['collection', 'non-existent']; + const expExists = this.mockProto.expects('exists') + .twice() + .withArgs(sinon.match('collection').or(sinon.match('non-existent'))); + + expExists.onFirstCall().returns(true); // with collection + expExists.onSecondCall().returns(false); // with non-existent + + assert.instanceOf(factory.unregisterAll(input), Object); + assert.equal(0, factory._factories.size); + }); + + it('Should NOT unregister an empty list of factory paths', () => { + assert.instanceOf(factory.unregisterAll(), Object); + assert.equal(0, factory._factories.size); + }); + + }); + + describe('find()', () => { + + it('Should return the factory (factory found)', () => { + factory.registerAll(['collection', 'stack', 'queue']); // Setup a few factories + const input = 'stack'; + + const exp = factory.find(input); + assert.isNotNull(exp); + assert.equal(factory._resolve(input), exp); + }); + + it('Should return null (factory not found)', () => { + const input = 'non-existent'; + const exp = factory.find(input); + assert.isNull(exp); + assert.isNull(factory.find()); + }); + + }); + + describe('exists()', () => { + + it('Should return true (factory exists)', () => { + assert.isTrue(factory.exists('collection')); + }); + + it('Should return false (factory doesn\'t exists)', () => { + assert.isFalse(factory.exists('non-existent')); + assert.isFalse(factory.exists()); + }); + + }); + + describe('get()', () => { + + it('Should get a new instance', () => { + assert.instanceOf(factory.get('collection'), Collection); + }); + + it('Should NOT get a new instance (factory is not registered)', () => { + assert.isNull(factory.get('non-existent')); + }); + + }); + +}); From 998feca14cdbdab66128472f893438dbeef2a1b7 Mon Sep 17 00:00:00 2001 From: kuakman <3dimentionar@gmail.com> Date: Fri, 10 Mar 2017 07:45:19 -0800 Subject: [PATCH 18/39] util: checkpoint - added unit test coverage to commands.util.visitor classes. --- src/commands/util/visitor/visited.es6 | 29 ++++- src/commands/util/visitor/visitor.es6 | 2 +- test/commands/util/visitor/visited.spec.es6 | 121 ++++++++++++++++++++ test/commands/util/visitor/visitor.spec.es6 | 86 ++++++++++++++ 4 files changed, 234 insertions(+), 4 deletions(-) create mode 100644 test/commands/util/visitor/visited.spec.es6 create mode 100644 test/commands/util/visitor/visitor.spec.es6 diff --git a/src/commands/util/visitor/visited.es6 b/src/commands/util/visitor/visited.es6 index 558eb54..abeede7 100644 --- a/src/commands/util/visitor/visited.es6 +++ b/src/commands/util/visitor/visited.es6 @@ -6,22 +6,45 @@ import { EventEmitter } from 'events'; import _ from 'underscore'; import extend from 'extend'; import Visitor from 'commands/util/visitor/visitor'; +import InterfaceException from 'commands/util/exception/proxy/interface'; /** * Class Visited * @extends {events.EventEmitter} **/ -class Visited extends EventEmitter { +class Visited extends EventEmitter { /** * Constructor * @public + * @param {Any} target - instance to be visited * @param {Any} [...args] - constructor arguments * @return {commands.util.visitor.Visited} **/ - constructor(...args) { + constructor(target, ...args) { super(); - return new Proxy(extend(true, this, ...args), this); + return new Proxy(extend(true, this._valid(target), ...args), this); + } + + /** + * Validate Target to be visited + * @public + * @param {Any} target - instance to be visited + * @return {Any} + **/ + _valid(target) { + if(!_.defined(target)) throw InterfaceException.new('proxy'); + return target; + } + + /** + * Proxy getPrototypeOf trap override + * @public + * @param {Any} target - target reference + * @return {Object} + **/ + getPrototypeOf(target) { + return target.constructor.prototype; } /** diff --git a/src/commands/util/visitor/visitor.es6 b/src/commands/util/visitor/visitor.es6 index 40d83bd..9457905 100644 --- a/src/commands/util/visitor/visitor.es6 +++ b/src/commands/util/visitor/visitor.es6 @@ -35,7 +35,7 @@ class Visitor extends EventEmitter { **/ validate(vi) { if(!_.defined(vi)) return false; - if(!_.instanceOf(vi, Visited)) + if(!_.defined(vi.accept)) throw InterfaceException.new('interface', { name: 'commands.util.visitor.Visited' }); return true; } diff --git a/test/commands/util/visitor/visited.spec.es6 b/test/commands/util/visitor/visited.spec.es6 new file mode 100644 index 0000000..055945d --- /dev/null +++ b/test/commands/util/visitor/visited.spec.es6 @@ -0,0 +1,121 @@ +/** +* @module commands.util.visitor +* @author Patricio Ferreira <3dimentionar@gmail.com> +**/ +import Visited from 'commands/util/visitor/visited'; +import Visitor from 'commands/util/visitor/visitor'; +import InterfaceException from 'commands/util/exception/proxy/interface'; + +describe('commands.util.visitor.Visited', function() { + + before(() => { + this.sandbox = sinon.sandbox.create(); + }); + + beforeEach(() => { + this.mockProto = this.sandbox.mock(Visited.prototype); + this.mockVisitor = this.sandbox.mock(Visitor.prototype); + }); + + afterEach(() => { + this.mockProto.verify(); + this.mockVisitor.verify(); + this.sandbox.restore(); + delete this.mockProto; + delete this.mockVisitor; + }); + + after(() => { + delete this.sandbox; + }); + + describe('constructor', () => { + + it('Should get a new instance', () => { + const input = { option: true }; + assert.instanceOf(Visited.new(input, { property: 'Visited' }), Object); + assert.equal('Visited', input.property); + }); + + }); + + describe('_valid()', () => { + + it('Should throw InterfaceException: target is not defined', () => { + assert.throws(() => Visited.new(), InterfaceException.type.proxy()); + }); + + }); + + describe('proxy->get()', () => { + + it('Should proxify input with Visited: access to target property', () => { + const input = { option: true, method: this.sandbox.spy() }; + const exp = Visited.new(input, { property: 'fromVisited' }); + exp.method(); + + assert.equal(true, exp.option); + assert.equal('fromVisited', exp.property); + assert.isTrue(input.method.calledOnce); + assert.isNotNull(exp.accept); + }); + + }); + + describe('validate()', () => { + + it('Should return true', () => { + const inputVisitor = Visitor.new(); + const exp = Visited.new({}); + assert.isTrue(exp.validate(inputVisitor)); + }); + + it('Should return false: visitor not defined', () => { + const exp = Visited.new({}); + assert.isFalse(exp.validate()); + }); + + it('Should return false: visitor not implementing Visited', () => { + const exp = Visited.new({}); + assert.isFalse(exp.validate({})); + }); + + }); + + describe('accept()', () => { + + it('Should accept visitor', () => { + const inputVisitor = Visitor.new(); + const exp = Visited.new({}); + + const expValidate = this.mockProto.expects('validate') + .once() + .withArgs(inputVisitor) + .returns(true); + + const expVisit = this.mockVisitor.expects('visit') + .once() + .withArgs(exp) + .returns(exp); + + assert.instanceOf(exp.accept(inputVisitor), Object); + assert.isTrue(expValidate.calledBefore(expVisit)); + }); + + it('Should NOT accept visitor', () => { + const inputVisitor = Visitor.new(); + const exp = Visited.new({}); + + const expValidate = this.mockProto.expects('validate') + .once() + .withArgs(inputVisitor) + .returns(false); + + const expVisit = this.mockVisitor.expects('visit').never(); + + assert.instanceOf(exp.accept(inputVisitor), Object); + }); + + }); + +}); diff --git a/test/commands/util/visitor/visitor.spec.es6 b/test/commands/util/visitor/visitor.spec.es6 new file mode 100644 index 0000000..5006841 --- /dev/null +++ b/test/commands/util/visitor/visitor.spec.es6 @@ -0,0 +1,86 @@ +/** +* @module commands.util.visitor +* @author Patricio Ferreira <3dimentionar@gmail.com> +**/ +import Visitor from 'commands/util/visitor/visitor'; +import Visited from 'commands/util/visitor/visited'; +import InterfaceException from 'commands/util/exception/proxy/interface'; + +describe('commands.util.visitor.Visitor', function() { + + before(() => { + this.sandbox = sinon.sandbox.create(); + }); + + beforeEach(() => { + this.mockProto = this.sandbox.mock(Visitor.prototype); + }); + + afterEach(() => { + this.mockProto.verify(); + this.sandbox.restore(); + delete this.mockProto; + }); + + after(() => { + delete this.sandbox; + }); + + describe('constructor', () => { + + it('Should get a new instance', () => { + const exp = Visitor.new(); + assert.instanceOf(exp, Visitor); + assert.equal(exp.name, 'Visitor'); + }); + + }); + + describe('validate', () => { + + it('Should return true', () => { + const exp = Visitor.new(); + const input = Visited.new({}); + assert.isTrue(exp.validate(input)); + }); + + it('Should return false: visited not defined', () => { + const exp = Visitor.new(); + assert.isFalse(exp.validate()); + }); + + it('Should throw InterfaceException: visited doesn\'t implement his interface', () => { + const exp = Visitor.new(); + assert.throws(() => exp.validate({}), + InterfaceException.type.interface({ name: 'commands.util.visitor.Visited' })); + }); + + }); + + describe('visit', () => { + + it('Should visit the visted instance', () => { + const exp = Visitor.new(); + const input = Visited.new({}); + const expValidate = this.mockProto.expects('validate') + .once() + .withArgs(input) + .returns(true); + + assert.instanceOf(exp.visit(input), Object); + }); + + it('Should NOT visit the visted instance', () => { + const exp = Visitor.new(); + const input = {}; + const expValidate = this.mockProto.expects('validate') + .once() + .withArgs(input) + .returns(false); + + assert.isNull(exp.visit(input)); + }); + + }); + +}); From a064f217de2d476d564f83dd39f91427b1707ebe Mon Sep 17 00:00:00 2001 From: kuakman <3dimentionar@gmail.com> Date: Sat, 11 Mar 2017 18:02:39 -0800 Subject: [PATCH 19/39] cli: checkpoint - Refactores Visitor pattern to decorate commands.bin.SquareBox interface to load parse yags arguments and configuration via different methods. Progress on unit test coverage for commands.bin.Squarebox. --- src/commands/bin/sqbox.es6 | 71 +++++++++++++++---- src/commands/bin/visitor/commander.es6 | 21 ++++-- src/commands/command.es6 | 47 +++++++++--- .../util/exception/command/command.es6 | 39 ---------- src/commands/util/factory/factory.es6 | 10 +++ src/commands/util/proxy/json.es6 | 60 ++++------------ src/commands/util/visitor/visited.es6 | 36 ++++++---- src/commands/util/visitor/visitor.es6 | 5 +- test/commands/bin/sqbox.spec.es6 | 37 ++++++++++ test/commands/util/factory/factory.spec.es6 | 7 +- test/commands/util/proxy/json.spec.es6 | 28 ++++++-- test/commands/util/visitor/visited.spec.es6 | 59 +++++++++++---- 12 files changed, 271 insertions(+), 149 deletions(-) delete mode 100644 src/commands/util/exception/command/command.es6 create mode 100644 test/commands/bin/sqbox.spec.es6 diff --git a/src/commands/bin/sqbox.es6 b/src/commands/bin/sqbox.es6 index 62a0829..4f033bf 100644 --- a/src/commands/bin/sqbox.es6 +++ b/src/commands/bin/sqbox.es6 @@ -7,28 +7,47 @@ import extend from 'extend'; import Factory from 'commands/util/factory/factory'; import Command from 'commands/command'; -import Commander from 'commands/util/commander/commander'; -let enforcer = new Symbol(); +let enforcer = Symbol('SquareBox'); /** * Class SquareBox * @extends {commands.Command} * -* @uses {util.commander.Commander} +* @uses {commands.bin.visitor.Commander} **/ class SquareBox extends Command { /** * Constructor * @public - * @param {Symbol} pte - constructor enforcer - * @param [args = {}] {Object} Constructor arguments + * @param {Object} [args = {}] - Constructor arguments + * @return {commands.bin.SquareBox} + **/ + constructor(...args) { + super(...args); + return SquareBox.isPrivate(this.attachEvents()); + } + + /** + * Attaches Events + * @public + * @return {commands.bin.SquareBox} + **/ + attachEvents() { + // TODO + return this; + } + + /** + * Read all command arguments using {commands.bin.visitor.Commander} + * @public + * @async * @return {commands.bin.SquareBox} **/ - constructor(pte, ...args) { - super(); - return this.validate().register().settings(...args); + read() { + // TODO + return this; } /** @@ -38,7 +57,7 @@ class SquareBox extends Command { * @param {Symbol} pte - constructor enforcer * @return {commands.bin.SquareBox} **/ - validate(pte) { + isPrivate(pte) { if(!_.isEqual(pte, enforcer)) throw new Error('Private Violation'); return this; } @@ -46,10 +65,12 @@ class SquareBox extends Command { /** * Register Command Factories * @public + * @override * @return {commands.bin.SquareBox} **/ register() { - Factory.registerAll(Squarebox.commands); + super.register(); + Factory.registerAll(this.constructor.commands); return this; } @@ -59,19 +80,39 @@ class SquareBox extends Command { * @type {Array} **/ static commands = [ - 'help', - 'clean', - 'bundle', - 'visualize' + 'help/help', + 'clean/clean', + 'bundle/bundle', + 'visualize/visualize' ]; + /** + * SquareBox visitors + * @static + * @override + * @type {Array} + **/ + static visitors = [ + 'bin/visitor/commander' + ].concat(Command.visitors); + + /** + * Static enforcer validation + * @static + * @param {commands.bin.SquareBox} instance - squarebox instance reference + * @return {commands.bin.SquareBox} + **/ + static isPrivate(instance) { + return instance.isPrivate(enforcer); + } + /** * Static Run * @static * @return commands.bin.SquareBox **/ static run() { - return new Proxy(this.new(enforcer, { cwd: process.cwd() }), Commander.new()); + return this.new({ cwd: process.cwd() }).read(); } } diff --git a/src/commands/bin/visitor/commander.es6 b/src/commands/bin/visitor/commander.es6 index 34a211a..566dd25 100644 --- a/src/commands/bin/visitor/commander.es6 +++ b/src/commands/bin/visitor/commander.es6 @@ -1,5 +1,5 @@ /** -* @module commands.util.visitor +* @module commands.bin.visitor * @author Patricio Ferreira <3dimentionar@gmail.com> **/ import _ from 'underscore'; @@ -15,11 +15,24 @@ import QueueAsync from 'commands/util/adt/queue-async'; class Commander extends Visitor { /** - * Default arguments parsing + * Constructor * @public + * @override + * @param {Any} [...args] - constructor arguments * @return {commands.bin.visitor.Commander} **/ - async parse() { + constructor(...args) { + super(...args); + return extend(true, this, { queue: QueueAsync.new() }); + } + + /** + * Parses Configuration options + * @public + * @param {commands.util.proxy.Json} ctx - context reference + * @return {commands.bin.visitor.Commander} + **/ + parse(ctx) { // TODO: [js,json,uri] QueueAsync for executing for configuration return this; } @@ -64,7 +77,7 @@ class Commander extends Visitor { static options = [ 'config', 'source-scan', - 'source-extensions' + 'source-extensions', 'source-alias', 'target-destination', 'target-format' diff --git a/src/commands/command.es6 b/src/commands/command.es6 index 1b41b1a..37dd054 100644 --- a/src/commands/command.es6 +++ b/src/commands/command.es6 @@ -5,17 +5,17 @@ import { EventEmitter } from 'events'; import _ from './util/mixins'; import extend from 'extend'; -import JSON from './util/proxy/json'; -import Collection from './util/adt/collection'; -import CommandException from './util/exception/command/command'; +import Factory from 'commands/util/factory/factory'; +import Visited from 'commands/util/visitor/visited'; /** * Class Command -* @extends {events.EventEmitter} +* @extends {commands.util.visitor.Visited} * * @uses {commands.util.proxy.JSON} +* @uses {commands.util.proxy.Asynchronous} - TODO: Convert Asynchronous into Visitor for QueueAsync/StackAysnc **/ -class Command extends EventEmitter { +class Command extends Visited { /** * Constructor @@ -25,7 +25,7 @@ class Command extends EventEmitter { **/ constructor(args = {}) { super(); - return JSON.proxy(this.settings(args)); + return this.settings(args).register().acceptAll(); } /** @@ -35,7 +35,26 @@ class Command extends EventEmitter { * @return {commands.Command} **/ settings(options) { - return extend(true, this, Command.defaults, _.pick(options, this.constructor.options)); + return extend(true, this, Command.defaults, _.pick(options, Command.options)); + } + + /** + * Registers Visitors + * @public + * @return {commands.Command} + **/ + register() { + Factory.basePath(this.dirname).registerAll(this.constructor.visitors); + return this; + } + + /** + * Accepts All Visitors + * @public + * @return {command.Command} + **/ + acceptAll() { + return _.reduce(this.constructor.visitors, (memo, v) => memo.accept(Factory.get(v)), this); } /** @@ -142,13 +161,24 @@ class Command extends EventEmitter { return this.target().format; } + /** + * Command Visitors + * @static + * @type {Array} + **/ + static visitors = [ + 'util/proxy/json' + ]; + /** * Command Defaults * @static * @type {Object} **/ static defaults = { - env: 'development' + env: 'development', + dirname: __dirname, + cwd: process.cwd() }; /** @@ -158,6 +188,7 @@ class Command extends EventEmitter { **/ static options = [ 'env', + 'dirname', 'cwd' ]; diff --git a/src/commands/util/exception/command/command.es6 b/src/commands/util/exception/command/command.es6 deleted file mode 100644 index 89e7c10..0000000 --- a/src/commands/util/exception/command/command.es6 +++ /dev/null @@ -1,39 +0,0 @@ -/** -* @module commands.util.exception.command -* @author Patricio Ferreira <3dimentionar@gmail.com> -**/ -import _ from 'underscore'; -import extend from 'extend'; -import Exception from '../exception'; - -/** -* Class CommandException -* @extends {commands.util.exception.Exception} -**/ -class CommandException extends Exception { - - /** - * Constructor - * @public - * @override - * @param [...args] {Any} constructor attribute - * @return {commands.util.exception.command.CommandException} - **/ - constructor(...args) { - super(...args); - this.name = 'CommandException'; - return this; - } - - /** - * Command Exception types - * @public - * @type {Object} - **/ - static type = extend(true, {}, Exception.type, { - chain: _.template('Required parameter `command` to be an instance of `commands.Command`') - }); - -} - -export default CommandException; diff --git a/src/commands/util/factory/factory.es6 b/src/commands/util/factory/factory.es6 index c225234..9a032b2 100644 --- a/src/commands/util/factory/factory.es6 +++ b/src/commands/util/factory/factory.es6 @@ -26,6 +26,16 @@ class Factory extends EventEmitter { return extend(true, this, attrs, { _factories: new Map() }).basePath(); } + /** + * Resets factories + * @public + * @return {commands.util.factoryFactory} + **/ + reset() { + this._factories.clear(); + return this; + } + /** * Sets a base path * @public diff --git a/src/commands/util/proxy/json.es6 b/src/commands/util/proxy/json.es6 index 6e7cb7e..81eb9c5 100644 --- a/src/commands/util/proxy/json.es6 +++ b/src/commands/util/proxy/json.es6 @@ -3,47 +3,14 @@ * @author Patricio Ferreira <3dimentionar@gmail.com> **/ import _ from 'underscore'; +import Visitor from 'commands/util/visitor/visitor'; import InterfaceException from 'commands/util/exception/proxy/interface'; /** -* Interface JSON +* Class JSON +* @extends {commands.util.visitor.Visitor} **/ -class Json { - - /** - * Proxy's `getPrototypeOf` trap strategy - * @public - * @param target {Any} proxy's target - * @return {Object} - **/ - getPrototypeOf(target) { - return target.constructor.prototype; - } - - /** - * Proxy's `get` trap strategy - * @public - * @param target {Any} proxy target - * @param property {String} property name - * @param receiver {Any} proxy receiver - * @return {Any} - **/ - get(target, property, receiver) { - let value = _.defined(this[property]) ? this[property] : target[property]; - return _.isFunction(value) ? this._context(target, property, value) : value; - } - - /** - * Resolves proxified function binding with context - * @private - * @param target {Any} proxy target - * @param property {String} property name - * @param func {Function} proxified function - * @return {Function} - **/ - _context(target, property, func) { - return _.defined(this[property]) ? _.bind(func, target, this) : _.bind(func, target); - } +class Json extends Visitor { /** * Reducer Strategy to iterate over properties @@ -69,7 +36,8 @@ class Json { * @return {Object} **/ _clean(current, memo = {}) { - return _.reduce(current, this._reduce, memo, this); + let keys = Object.getOwnPropertyNames(current); + return _.reduce(keys, (m, k) => this._reduce(m, current[k], k), memo, this); } /** @@ -80,20 +48,16 @@ class Json { * @return {Object} **/ toJSON(ctx) { - return JSON.parse(JSON.stringify(ctx._clean(this))); + return JSON.parse(JSON.stringify(this._clean(ctx))); } /** - * Proxifies a given target with an instance of this class - * @static - * @throws {commands.util.exception.proxy.InterfaceException} - * @param target {Any} instance to proxify - * @param [...args] {Any} constructor arguments - * @return {commands.util.proxy.Json} + * Visitor Name + * @public + * @type {String} **/ - static proxy(target, ...args) { - if(!_.defined(target)) throw InterfaceException.new('proxy'); - return new Proxy(target, new this(...args)); + get name() { + return 'JsonVisitor'; } } diff --git a/src/commands/util/visitor/visited.es6 b/src/commands/util/visitor/visited.es6 index abeede7..0dfb18f 100644 --- a/src/commands/util/visitor/visited.es6 +++ b/src/commands/util/visitor/visited.es6 @@ -17,24 +17,24 @@ class Visited extends EventEmitter { /** * Constructor * @public - * @param {Any} target - instance to be visited * @param {Any} [...args] - constructor arguments * @return {commands.util.visitor.Visited} **/ - constructor(target, ...args) { + constructor(...args) { super(); - return new Proxy(extend(true, this._valid(target), ...args), this); + return extend(true, this, ...args); } /** - * Validate Target to be visited - * @public - * @param {Any} target - instance to be visited - * @return {Any} + * Resolves proxified function binding with context + * @private + * @param target {Any} proxy target + * @param property {String} property name + * @param func {Function} proxified function + * @return {Function} **/ - _valid(target) { - if(!_.defined(target)) throw InterfaceException.new('proxy'); - return target; + _context(target, property, func) { + return _.defined(this[property]) ? this[property] : _.bind(func, target, this); } /** @@ -44,20 +44,30 @@ class Visited extends EventEmitter { * @return {Object} **/ getPrototypeOf(target) { - return target.constructor.prototype; + return this.constructor.prototype; + } + + /** + * Proxy ownKeys trap override + * @public + * @param {Any} target - visited proxy target + * @return {Array} + **/ + ownKeys(target) { + return _.keys(this); } /** * Proxy get trap override * @public - * @override * @param {Any} target - visited target reference * @param {String} property - visited target property * @param {Any} receiver - visited target constructor * @return {Any} **/ get(target, property, receiver) { - return _.defined(target[property]) ? target[property] : this[property]; + let value = _.defined(this[property]) ? this[property] : target[property]; + return _.isFunction(value) ? this._context(target, property, value) : value; } /** diff --git a/src/commands/util/visitor/visitor.es6 b/src/commands/util/visitor/visitor.es6 index 9457905..397bd1b 100644 --- a/src/commands/util/visitor/visitor.es6 +++ b/src/commands/util/visitor/visitor.es6 @@ -45,10 +45,11 @@ class Visitor extends EventEmitter { * This method is most likely to be overriden by subsclasses of this visitor. * @public * @param {commands.util.visitor.Visited} vi - instance to be visited by this visitor + * @param {Any} [...args] - arguments passed to the vistior who visit the current visited instance * @return {commands.util.visitor.Visited} **/ - visit(vi) { - return this.validate(vi) ? vi : null; + visit(vi, ...args) { + return this.validate(vi) ? new Proxy(this, vi) : null; } /** diff --git a/test/commands/bin/sqbox.spec.es6 b/test/commands/bin/sqbox.spec.es6 new file mode 100644 index 0000000..0c5c481 --- /dev/null +++ b/test/commands/bin/sqbox.spec.es6 @@ -0,0 +1,37 @@ +/** +* @module commands.bin +* @author Patricio Ferreira <3dimentionar@gmail.com> +**/ +describe('commands.bin.SquareBox', function() { + + before(() => { + this.sandbox = sinon.sandbox.create(); + }); + + beforeEach(() => { + if(this.sqbox) { + this.mockProto = this.sandbox.mock(this.sqbox); + } + }); + + afterEach(() => { + if(this.mockProto) { + this.mockProto.verify(); + this.sandbox.restore(); + delete this.mockProto; + } + }); + + after(() => { + delete this.sandbox; + }); + + describe('constructor()', () => { + + it('Should get an instance', () => { + this.sqbox = require('commands/bin/sqbox'); + }); + + }); + +}); diff --git a/test/commands/util/factory/factory.spec.es6 b/test/commands/util/factory/factory.spec.es6 index d85e3c9..67f30be 100644 --- a/test/commands/util/factory/factory.spec.es6 +++ b/test/commands/util/factory/factory.spec.es6 @@ -10,20 +10,24 @@ import Collection from 'commands/util/adt/collection'; describe('commands.util.factory.Factory', function() { before(() => { + factory.reset(); this.sandbox = sinon.sandbox.create(); }); beforeEach(() => { + this.mockCommand = this.sandbox.mock(Command); this.mockLogger = this.sandbox.mock(logger); this.mockProto = this.sandbox.mock(factory); }); afterEach(() => { + this.mockCommand.verify(); this.mockLogger.verify(); this.mockProto.verify(); this.sandbox.restore(); + delete this.mockCommand; delete this.mockLogger; delete this.mockProto; }); @@ -101,7 +105,8 @@ describe('commands.util.factory.Factory', function() { }); it('Should instanciate factory using static constructor', () => { - assert.instanceOf(factory._new(Command), Command); + const expNew = this.mockCommand.expects('new').returns('Command'); + assert.equal(factory._new(Command), 'Command'); }); it('Should instanciate factory using operator `new`', () => { diff --git a/test/commands/util/proxy/json.spec.es6 b/test/commands/util/proxy/json.spec.es6 index 8e10278..172f3e4 100644 --- a/test/commands/util/proxy/json.spec.es6 +++ b/test/commands/util/proxy/json.spec.es6 @@ -4,6 +4,7 @@ **/ import _ from 'underscore'; import Json from 'commands/util/proxy/json'; +import Visited from 'commands/util/visitor/visited'; import Command from 'commands/command'; import InterfaceException from 'commands/util/exception/proxy/interface'; @@ -33,6 +34,7 @@ describe('commands.util.proxy.Json', function() { }); afterEach(() => { + this.mockJson.verify(); this.sandbox.restore(); delete this.target; delete this.mockJson; @@ -45,11 +47,24 @@ describe('commands.util.proxy.Json', function() { describe('constructor()', () => { it('Should get a new instance', () => { - assert.instanceOf(Json.proxy(this.target), Function); + assert.instanceOf(Json.new(), Json); }); - it('Should Error: target is not defined', () => { - assert.throws(() => Json.proxy(), InterfaceException.type.proxy()); + }); + + describe('visit()', () => { + + it('Should visit the target visited', () => { + const input = Visited.new({ property: 'target' }); + const exp = Json.new(); + assert.instanceOf(exp.visit(input), Visited); + }); + + it('Should NOT visit the target visited', () => { + const input = { property: 'target' }; + const exp = Json.new(); + assert.throws(() => exp.visit(input), InterfaceException.type + .interface({ name: 'commands.util.visitor.Visited' })); }); }); @@ -57,10 +72,11 @@ describe('commands.util.proxy.Json', function() { describe('toJSON()', () => { it('Should return a json representation', () => { - const o = this.target(); - const exp = Json.proxy(o); + const input = this.target(); + const visited = Visited.new(input); + const exp = Json.new().visit(visited); const out = exp.toJSON(); - assert.notEqual(o, out); + assert.notDeepEqual(input, out); }); }); diff --git a/test/commands/util/visitor/visited.spec.es6 b/test/commands/util/visitor/visited.spec.es6 index 055945d..5d24432 100644 --- a/test/commands/util/visitor/visited.spec.es6 +++ b/test/commands/util/visitor/visited.spec.es6 @@ -32,31 +32,64 @@ describe('commands.util.visitor.Visited', function() { describe('constructor', () => { it('Should get a new instance', () => { - const input = { option: true }; - assert.instanceOf(Visited.new(input, { property: 'Visited' }), Object); - assert.equal('Visited', input.property); + const exp = Visited.new({ property: 'Visited' }); + assert.instanceOf(exp, Visited); + assert.equal('Visited', exp.property); }); }); - describe('_valid()', () => { + describe('getPrototypeOf', () => { - it('Should throw InterfaceException: target is not defined', () => { - assert.throws(() => Visited.new(), InterfaceException.type.proxy()); + it('Should return visited prototype when instanceOf evaluation occur', () => { + const target = function() {}; + const exp = Visited.new({}); + assert.equal(exp.getPrototypeOf(target), exp.constructor.prototype); }); }); - describe('proxy->get()', () => { + describe('ownKeys()', () => { + + it('Should always return visited own keys on proxified target', () => { + const target = { name: 'Target' }; + const exp = Visited.new({ name: 'visited' }); + assert.lengthOf(_.keys(exp), exp.ownKeys(target).length); + }); + + }); + + describe('get()', () => { it('Should proxify input with Visited: access to target property', () => { - const input = { option: true, method: this.sandbox.spy() }; - const exp = Visited.new(input, { property: 'fromVisited' }); - exp.method(); + const input = { target: 'fromTarget', targetMethod: this.sandbox.spy() }; + const exp = Visited.new({ visited: 'fromVisited', visitedMethod: this.sandbox.spy() }); + + // Target Visitor + exp.get(input, 'targetMethod')(); + assert.equal('fromTarget', exp.get(input, 'target')); + assert.isTrue(input.targetMethod.calledOnce); + assert.equal(input.targetMethod.getCall(0).args[0], exp); + + // Visited + exp.get(input, 'visitedMethod')(); + assert.equal('fromVisited', exp.get(input, 'visited')); + assert.isTrue(exp.visitedMethod.calledOnce); + assert.lengthOf(exp.visitedMethod.getCall(0).args, 0); + + assert.isNotNull(exp.accept); + }); + + it('Should proxify the visited always if property/function names have the same name/key', () => { + const input = { property: 'fromTarget', method: this.sandbox.spy() }; + const exp = Visited.new({ property: 'fromVisited', method: this.sandbox.spy() }); + + // Always Visited + exp.get(input, 'method')(); + assert.equal('fromVisited', exp.get(input, 'property')); + assert.isTrue(exp.method.calledOnce); + assert.isFalse(input.method.called); - assert.equal(true, exp.option); - assert.equal('fromVisited', exp.property); - assert.isTrue(input.method.calledOnce); assert.isNotNull(exp.accept); }); From 77bb6ad852635807706024181185becf8f75fb5d Mon Sep 17 00:00:00 2001 From: kuakman <3dimentionar@gmail.com> Date: Sun, 12 Mar 2017 00:10:08 -0800 Subject: [PATCH 20/39] cli: checkpoint - Refactored visitors into commands.visitors package instead of commands.util.proxy. Converted commands.visitors.async.Asynchronous into a Visitor. Added unit test coverage to Asynchronous Visitor. Fixed unit tests against QueueAsync and StackAsync. --- src/commands/bin/sqbox.es6 | 2 +- src/commands/command.es6 | 46 +++++++---- src/commands/util/adt/queue-async.es6 | 36 ++++----- src/commands/util/adt/stack-async.es6 | 14 ++-- src/commands/util/proxy/async.es6 | 66 ---------------- src/commands/util/visitor/visited.es6 | 8 +- src/commands/util/visitor/visitor.es6 | 2 +- src/commands/visitors/async/async.es6 | 51 ++++++++++++ .../{bin/visitor => visitors}/commander.es6 | 8 +- .../visitors/configuration/remote.es6 | 0 .../proxy => visitors/formatter}/json.es6 | 14 ++-- test/commands/util/adt/queue-async.spec.es6 | 17 ++-- test/commands/util/adt/stack-async.spec.es6 | 17 ++-- test/commands/util/proxy/async.spec.es6 | 54 ------------- test/commands/visitors/async/async.spec.es6 | 79 +++++++++++++++++++ test/commands/visitors/commander.spec.es6 | 0 .../formatter}/json.spec.es6 | 14 ++-- 17 files changed, 221 insertions(+), 207 deletions(-) delete mode 100644 src/commands/util/proxy/async.es6 create mode 100644 src/commands/visitors/async/async.es6 rename src/commands/{bin/visitor => visitors}/commander.es6 (88%) create mode 100644 src/commands/visitors/configuration/remote.es6 rename src/commands/{util/proxy => visitors/formatter}/json.es6 (77%) delete mode 100644 test/commands/util/proxy/async.spec.es6 create mode 100644 test/commands/visitors/async/async.spec.es6 create mode 100644 test/commands/visitors/commander.spec.es6 rename test/commands/{util/proxy => visitors/formatter}/json.spec.es6 (82%) diff --git a/src/commands/bin/sqbox.es6 b/src/commands/bin/sqbox.es6 index 4f033bf..c03f7f4 100644 --- a/src/commands/bin/sqbox.es6 +++ b/src/commands/bin/sqbox.es6 @@ -93,7 +93,7 @@ class SquareBox extends Command { * @type {Array} **/ static visitors = [ - 'bin/visitor/commander' + 'visitors/commander' ].concat(Command.visitors); /** diff --git a/src/commands/command.es6 b/src/commands/command.es6 index 37dd054..4620a42 100644 --- a/src/commands/command.es6 +++ b/src/commands/command.es6 @@ -12,8 +12,8 @@ import Visited from 'commands/util/visitor/visited'; * Class Command * @extends {commands.util.visitor.Visited} * -* @uses {commands.util.proxy.JSON} -* @uses {commands.util.proxy.Asynchronous} - TODO: Convert Asynchronous into Visitor for QueueAsync/StackAysnc +* @uses {commands.visitors.formatter.Json} +* @uses {commands.visitors.async.Asynchronous} **/ class Command extends Visited { @@ -59,7 +59,9 @@ class Command extends Visited { /** * Proxified asynchronous next strategy + * FIXME: Implement when the command gets rejected. * @public + * @override * @param adt {commands.util.proxy.Asynchronous} adt used for asynchronous operations * @param resolve {Function} asynchronous promise's resolve * @param reject {Function} asynchronous promise's reject @@ -71,12 +73,29 @@ class Command extends Visited { } /** - * Default Command Run + * Default hook before run + * @public + * @return {commands.Command} + **/ + before() { + return this.pending(); + } + + /** + * Default Run * @public * @return {commands.Command} **/ run() { - this.pending(); + return this.before().after(); + } + + /** + * Default hook after run + * @public + * @return {commands.Command} + **/ + after() { return this.done(); } @@ -86,7 +105,8 @@ class Command extends Visited { * @return {command.Command} **/ pending() { - return this.emit(Command.events.pending, this); + this.emit(Command.events.pending, this); + return this; } /** @@ -95,7 +115,8 @@ class Command extends Visited { * @return {command.Command} **/ done() { - return this.emit(Command.events.done, this); + this.emit(Command.events.done, this); + return this; } /** @@ -167,7 +188,8 @@ class Command extends Visited { * @type {Array} **/ static visitors = [ - 'util/proxy/json' + 'visitors/formatter/json', + 'visitors/async/async' ]; /** @@ -202,16 +224,6 @@ class Command extends Visited { done: 'commands:command:done' }; - /** - * Static Constructor - * @static - * @param [...agrs] {Any} constructor arguments - * @return {commands.Command} - **/ - static new(...args) { - return new this(...args); - } - } export default Command; diff --git a/src/commands/util/adt/queue-async.es6 b/src/commands/util/adt/queue-async.es6 index 52e3dc7..64f7457 100644 --- a/src/commands/util/adt/queue-async.es6 +++ b/src/commands/util/adt/queue-async.es6 @@ -5,14 +5,13 @@ import _ from 'underscore'; import extend from 'extend'; import Queue from 'commands/util/adt/queue'; -import InterfaceException from 'commands/util/exception/proxy/interface'; -import Asynchronous from 'commands/util/proxy/async'; +import Asynchronous from 'commands/visitors/async/async'; /** * Class QueueAsync * Defines the interface of an asynchronous FIFO Queue (FirstIn-FirstOut). -* Interface for objects on this queue, must implement method `next` of {@link commands.util.proxy.Asynchronous} -* for promise resolution. +* Interface for objects on this queue, **must** implement method `next` of +* {@link commands.visitors.async.Asynchronous} for promise resolution. * @example *
Usage
* @@ -28,25 +27,26 @@ class QueueAsync extends Queue { * Constructor * @public * @override - * @param [initial = []] {Array} Initial Array - * @param [opts = {}] {Object} collection options + * @param {Array} [initial = []] - initial elements + * @param {Object} [opts = {}] - collection options * @return {commands.util.adt.QueueAsync} **/ constructor(initial = [], opts = {}) { - super(initial, opts); - return extend(true, this, { _last: [] }); + return super(initial, extend(true, opts, { _visitor: Asynchronous.new(), _last: [] })); } /** * Default instanciation strategy for new elements added in this collection * @private * @override - * @param e {Any} element to instanciate - * @param opts {Object} additional options + * @param {Any} e - element to instanciate + * @param {Object} opts - additional options * @return {Any} **/ _new(e, opts) { - return Asynchronous.proxy(super._new(e, opts), this); + let element = super._new(e, opts); + this._visitor.validate(element); + return element.accept(this._visitor); } /** @@ -64,7 +64,7 @@ class QueueAsync extends Queue { * @public * @async * @override - * @param [opts = {}] {Object} additional options + * @param {Object} [opts = {}] - additional options * @return {Promise} **/ async poll(opts = {}) { @@ -76,22 +76,20 @@ class QueueAsync extends Queue { * Asynchronous Queue next * @public * @emits {QueueAsync.events.next} - when opts.silent is false or undefined - * @param [opts] {Object} additional options + * @param {Object} [opts] - additional options * @return {Promise} **/ next(opts) { let element = super.poll(opts); - if(!_.defined(element.next)) - throw InterfaceException.new('interface', { name: 'commands.util.proxy.Asynchronous' }); if(!opts.silent) this.emit(QueueAsync.events.next, element); - return element.do(this); + return element.execute(this); } /** * Retrieves and removes the head of this queue, or returns null if this queue is empty * @public - * @param res {Promise} current promise (resolved or rejected) - * @param [opts] {Object} additional options + * @param {Promise} res - current promise (resolved or rejected) + * @param {Object} [opts] - additional options * @return {Any} **/ onNext(res, opts) { @@ -103,7 +101,7 @@ class QueueAsync extends Queue { * Asynchronous Queue end * @public * @emits {QueueAsync.events.end} - when opts.silent is false or undefined - * @param [opts = {}] {Object} additional options + * @param {Object} [opts = {}] - additional options * @return {commands.util.adt.QueueAsync} **/ end(opts) { diff --git a/src/commands/util/adt/stack-async.es6 b/src/commands/util/adt/stack-async.es6 index 6142483..4bebd23 100644 --- a/src/commands/util/adt/stack-async.es6 +++ b/src/commands/util/adt/stack-async.es6 @@ -5,8 +5,7 @@ import _ from 'underscore'; import extend from 'extend'; import Stack from 'commands/util/adt/stack'; -import InterfaceException from 'commands/util/exception/proxy/interface'; -import Asynchronous from 'commands/util/proxy/async'; +import Asynchronous from 'commands/visitors/async/async'; /** * Class StackAsync @@ -33,8 +32,7 @@ class StackAsync extends Stack { * @return {commands.util.adt.StackAsync} **/ constructor(initial = [], opts = {}) { - super(initial, opts); - return extend(true, this, { _last: [] }); + return super(initial, extend(true, opts, { _visitor: Asynchronous.new(), _last: [] })); } /** @@ -46,7 +44,9 @@ class StackAsync extends Stack { * @return {Any} **/ _new(e, opts) { - return Asynchronous.proxy(super._new(e, opts), this); + let element = super._new(e, opts); + this._visitor.validate(element); + return element.accept(this._visitor); } /** @@ -81,10 +81,8 @@ class StackAsync extends Stack { **/ next(opts) { let element = super.pop(opts); - if(!_.defined(element.next)) - throw InterfaceException.new('interface', { name: 'commands.util.proxy.Asynchronous' }); if(!opts.silent) this.emit(StackAsync.events.next, element); - return element.do(this); + return element.execute(this); } /** diff --git a/src/commands/util/proxy/async.es6 b/src/commands/util/proxy/async.es6 deleted file mode 100644 index 8ff61ee..0000000 --- a/src/commands/util/proxy/async.es6 +++ /dev/null @@ -1,66 +0,0 @@ -/** -* @module commands.util.proxy -* @author Patricio Ferreira <3dimentionar@gmail.com> -**/ -import { EventEmitter } from 'events'; -import _ from 'underscore'; -import InterfaceException from 'commands/util/exception/proxy/interface'; - -/** -* Interface Asynchronous -* @extends events.EventEmitter -**/ -class Asynchronous extends EventEmitter { - - /** - * Proxy's `get` trap strategy - * @public - * @param target {Any} proxy target - * @param property {String} property name - * @param receiver {Any} proxy receiver - * @return {Any} - **/ - get(target, property) { - let value = _.defined(this[property]) ? this[property] : target[property]; - return _.isFunction(value) ? this._context(target, property, value) : value; - } - - /** - * Resolves proxified function binding with context - * @private - * @param target {Any} proxy target - * @param property {String} property name - * @param func {Function} proxified function - * @return {Function} - **/ - _context(target, property, func) { - return _.defined(this[property]) ? _.bind(func, target, this) : _.bind(func, target); - } - - /** - * Default strategy to perform an asynchronous operation - * @public - * @param [ctx] {commands.util.proxy.Asynchronous} context reference - * @param adt {commands.util.proxy.Asynchronous} reference to adt using this proxy on their elements - * @return {Promise} - **/ - do(ctx, adt) { - return new Promise((resolve, reject) => this.next(adt, resolve, reject)); - } - - /** - * Proxifies a given target with an instance of this class - * @static - * @throws {commands.util.exception.proxy.InterfaceException} - * @param target {Any} instance to proxify - * @param [...args] {Any} constructor arguments - * @return {commands.util.proxy.Asynchronous} - **/ - static proxy(target, ...args) { - if(!_.defined(target)) throw InterfaceException.new('proxy'); - return new Proxy(target, new this(...args)); - } - -} - -export default Asynchronous; diff --git a/src/commands/util/visitor/visited.es6 b/src/commands/util/visitor/visited.es6 index 0dfb18f..57abcc6 100644 --- a/src/commands/util/visitor/visited.es6 +++ b/src/commands/util/visitor/visited.es6 @@ -28,9 +28,9 @@ class Visited extends EventEmitter { /** * Resolves proxified function binding with context * @private - * @param target {Any} proxy target - * @param property {String} property name - * @param func {Function} proxified function + * @param {Any} target - proxy target + * @param {String} property - property name + * @param {Function} func - proxified function * @return {Function} **/ _context(target, property, func) { @@ -93,7 +93,7 @@ class Visited extends EventEmitter { /** * Static Constructor * @static - * @param [...args] {Any} constructor arguments + * @param {Any} [...args] - constructor arguments * @return {commands.util.visitor.Visited} **/ static new(...args) { diff --git a/src/commands/util/visitor/visitor.es6 b/src/commands/util/visitor/visitor.es6 index 397bd1b..2003dd3 100644 --- a/src/commands/util/visitor/visitor.es6 +++ b/src/commands/util/visitor/visitor.es6 @@ -64,7 +64,7 @@ class Visitor extends EventEmitter { /** * Static Constructor * @static - * @param [...args] {Any} constructor arguments + * @param {Any} [...args] - constructor arguments * @return {commands.util.visitor.Visitor} **/ static new(...args) { diff --git a/src/commands/visitors/async/async.es6 b/src/commands/visitors/async/async.es6 new file mode 100644 index 0000000..7cd3197 --- /dev/null +++ b/src/commands/visitors/async/async.es6 @@ -0,0 +1,51 @@ +/** +* @module commands.visitors.async +* @author Patricio Ferreira <3dimentionar@gmail.com> +**/ +import { EventEmitter } from 'events'; +import _ from 'underscore'; +import Visitor from 'commands/util/visitor/visitor'; + +/** +* Class Asynchronous +* @extends {commands.util.visitor.Visitor} +**/ +class Asynchronous extends Visitor { + + /** + * Default Next Strategy to always resolve the promise. + * Note: This method was designed (and it's most likely) to be overriden by + * {@link commands.util.visitor.Visited} subclasses that use this visitor. + * @public + * @param adt {commands.util.proxy.Asynchronous} adt used for asynchronous operations + * @param resolve {Function} asynchronous promise's resolve + * @param reject {Function} asynchronous promise's reject + * @return {Promise} + **/ + next(adt, resolve, reject) { + return resolve(); + } + + /** + * Default strategy to perform an asynchronous operation + * @public + * @param {commands.util.visitor.Visited} [ctx] - context reference + * @param {commands.visitors.async.Asynchronous} adt - reference to adt using this interface on their elements + * @return {Promise} + **/ + execute(ctx, adt) { + return new Promise((resolve, reject) => ctx.next(adt, resolve, reject)); + } + + /** + * Visitor Name + * @public + * @type {String} + **/ + get name() { + return 'AsynchronousVisitor'; + } + +} + +export default Asynchronous; diff --git a/src/commands/bin/visitor/commander.es6 b/src/commands/visitors/commander.es6 similarity index 88% rename from src/commands/bin/visitor/commander.es6 rename to src/commands/visitors/commander.es6 index 566dd25..49713c3 100644 --- a/src/commands/bin/visitor/commander.es6 +++ b/src/commands/visitors/commander.es6 @@ -1,5 +1,5 @@ /** -* @module commands.bin.visitor +* @module commands.visitors * @author Patricio Ferreira <3dimentionar@gmail.com> **/ import _ from 'underscore'; @@ -19,7 +19,7 @@ class Commander extends Visitor { * @public * @override * @param {Any} [...args] - constructor arguments - * @return {commands.bin.visitor.Commander} + * @return {commands.visitors.Commander} **/ constructor(...args) { super(...args); @@ -29,8 +29,8 @@ class Commander extends Visitor { /** * Parses Configuration options * @public - * @param {commands.util.proxy.Json} ctx - context reference - * @return {commands.bin.visitor.Commander} + * @param {commands.util.visitor.Visited} ctx - context reference + * @return {commands.visitors.Commander} **/ parse(ctx) { // TODO: [js,json,uri] QueueAsync for executing for configuration diff --git a/src/commands/visitors/configuration/remote.es6 b/src/commands/visitors/configuration/remote.es6 new file mode 100644 index 0000000..e69de29 diff --git a/src/commands/util/proxy/json.es6 b/src/commands/visitors/formatter/json.es6 similarity index 77% rename from src/commands/util/proxy/json.es6 rename to src/commands/visitors/formatter/json.es6 index 81eb9c5..995a349 100644 --- a/src/commands/util/proxy/json.es6 +++ b/src/commands/visitors/formatter/json.es6 @@ -1,5 +1,5 @@ /** -* @module commands.util.proxy +* @module commands.visitors.formatter * @author Patricio Ferreira <3dimentionar@gmail.com> **/ import _ from 'underscore'; @@ -15,9 +15,9 @@ class Json extends Visitor { /** * Reducer Strategy to iterate over properties * @public - * @param m {Object} memoized object reference - * @param v {Any} current object's value - * @param k {String} current object's key + * @param {Object} m - memoized object reference + * @param {Any} v - current object's value + * @param {String} k - current object's key * @return {Object} **/ _reduce(m, v, k) { @@ -31,8 +31,8 @@ class Json extends Visitor { /** * Clean Functions from JSON representation * @public - * @param current {Any} current object - * @param [memo = {}] {Object} memoized object + * @param {Any} current - current object + * @param {Object} [memo = {}] - memoized object * @return {Object} **/ _clean(current, memo = {}) { @@ -44,7 +44,7 @@ class Json extends Visitor { * Returns a json representation of the instance of this class * This method uses recursion * @public - * @param [ctx] {commands.util.proxy.Json} context reference + * @param {commands.visitors.formatter.Json} [ctx] - context reference * @return {Object} **/ toJSON(ctx) { diff --git a/test/commands/util/adt/queue-async.spec.es6 b/test/commands/util/adt/queue-async.spec.es6 index eb06bec..e39ca0a 100644 --- a/test/commands/util/adt/queue-async.spec.es6 +++ b/test/commands/util/adt/queue-async.spec.es6 @@ -4,6 +4,7 @@ **/ import QueueAsync from 'commands/util/adt/queue-async'; import Command from 'commands/command'; +import Asynchronous from 'commands/visitors/async/async'; import InterfaceException from 'commands/util/exception/proxy/interface'; describe('commands.util.adt.QueueAsync', function() { @@ -34,7 +35,9 @@ describe('commands.util.adt.QueueAsync', function() { describe('constructor()', () => { it('Should get a new instance', () => { - assert.instanceOf(QueueAsync.new([], { capacity: 3 }), QueueAsync); + const exp = QueueAsync.new([], { capacity: 3 }); + assert.instanceOf(exp, QueueAsync); + assert.instanceOf(exp._visitor, Asynchronous); }); it('Should get a new instance (no initial and no opts)', () => { @@ -77,15 +80,9 @@ describe('commands.util.adt.QueueAsync', function() { }).catch((err) => console.log(err)); }); - it('Should Error: Element doesn\'t implement commands.util.proxy.Asynchronous#next()', (done) => { - const message = InterfaceException.type.interface({ name: 'commands.util.proxy.Asynchronous' }); - const exp = QueueAsync.new([{ simple: true }], { capacity: 1 }); - - exp.poll().catch((err) => { - assert.instanceOf(err, Error); - assert.equal(err.message, message); - done(); - }); + it('Should Error: Element doesn\'t implement interface commands.util.visitor.Visited', () => { + const message = InterfaceException.type.interface({ name: 'commands.util.visitor.Visited' }); + assert.throws(() => QueueAsync.new([{ simple: true }], { capacity: 1 }), message); }); }); diff --git a/test/commands/util/adt/stack-async.spec.es6 b/test/commands/util/adt/stack-async.spec.es6 index aa06e3f..ef137ff 100644 --- a/test/commands/util/adt/stack-async.spec.es6 +++ b/test/commands/util/adt/stack-async.spec.es6 @@ -4,6 +4,7 @@ **/ import StackAsync from 'commands/util/adt/stack-async'; import Command from 'commands/command'; +import Asynchronous from 'commands/visitors/async/async'; import InterfaceException from 'commands/util/exception/proxy/interface'; describe('commands.util.adt.StackAsync', function() { @@ -34,7 +35,9 @@ describe('commands.util.adt.StackAsync', function() { describe('constructor()', () => { it('Should get a new instance', () => { - assert.instanceOf(StackAsync.new([{ option: true }]), StackAsync); + const exp = StackAsync.new([]); + assert.instanceOf(exp, StackAsync); + assert.instanceOf(exp._visitor, Asynchronous); }); it('Should get a new instance (no initial and no opts)', () => { @@ -77,15 +80,9 @@ describe('commands.util.adt.StackAsync', function() { }).catch((err) => console.log(err)); }); - it('Should Error: Element doesn\'t implement commands.util.proxy.Asynchronous#next()', (done) => { - const message = InterfaceException.type.interface({ name: 'commands.util.proxy.Asynchronous' }); - const exp = StackAsync.new([{ simple: true }]); - - exp.pop().catch((err) => { - assert.instanceOf(err, Error); - assert.equal(err.message, message); - done(); - }); + it('Should Error: Element doesn\'t implement interface commands.util.visitor.Visited', () => { + const message = InterfaceException.type.interface({ name: 'commands.util.visitor.Visited' }); + assert.throws(() => StackAsync.new([{ simple: true }]), message); }); }); diff --git a/test/commands/util/proxy/async.spec.es6 b/test/commands/util/proxy/async.spec.es6 deleted file mode 100644 index 51ea326..0000000 --- a/test/commands/util/proxy/async.spec.es6 +++ /dev/null @@ -1,54 +0,0 @@ -/** -* @module commands.util.proxy -* @author Patricio Ferreira <3dimentionar@gmail.com> -**/ -import _ from 'underscore'; -import Asynchronous from 'commands/util/proxy/async'; -import Command from 'commands/command'; -import InterfaceException from 'commands/util/exception/proxy/interface'; - -describe('commands.util.proxy.Asynchronous', function() { - - before(() => { - this.sandbox = sinon.sandbox.create(); - }); - - beforeEach(() => { - this.target = () => { return { next: (adt, resolve, reject) => {} }; }; - this.mockAsync = this.sandbox.mock(Asynchronous.prototype); - }); - - afterEach(() => { - this.sandbox.restore(); - delete this.target; - delete this.mockAsync; - }); - - after(() => { - delete this.sandbox; - }); - - describe('constructor()', () => { - - it('Should get a new instance', () => { - assert.instanceOf(Asynchronous.proxy(this.target), Function); - }); - - it('Should Error: target is not defined', () => { - assert.throws(() => Asynchronous.proxy(), InterfaceException.type.proxy()); - }); - - }); - - describe('do()', () => { - - it('Should return a decorated target with asynchronous capabilities', () => { - const o = this.target(); - const exp = Asynchronous.proxy(o); - assert.isNotNull(exp.do); - assert.instanceOf(exp.do(o), Promise); - }); - - }); - -}); diff --git a/test/commands/visitors/async/async.spec.es6 b/test/commands/visitors/async/async.spec.es6 new file mode 100644 index 0000000..b1ca84c --- /dev/null +++ b/test/commands/visitors/async/async.spec.es6 @@ -0,0 +1,79 @@ +/** +* @module commands.visitors.async +* @author Patricio Ferreira <3dimentionar@gmail.com> +**/ +import _ from 'underscore'; +import Asynchronous from 'commands/visitors/async/async'; +import Visited from 'commands/util/visitor/visited'; +import InterfaceException from 'commands/util/exception/proxy/interface'; + +describe('commands.visitors.async.Asynchronous', function() { + + before(() => { + this.sandbox = sinon.sandbox.create(); + }); + + beforeEach(() => { + this.target = () => { return { next: (adt, resolve, reject) => {} }; }; + this.mockAsync = this.sandbox.mock(Asynchronous.prototype); + }); + + afterEach(() => { + this.sandbox.restore(); + delete this.target; + delete this.mockAsync; + }); + + after(() => { + delete this.sandbox; + }); + + describe('constructor()', () => { + + it('Should get a new instance', () => { + const exp = Asynchronous.new(this.target()); + assert.instanceOf(exp, Asynchronous); + assert.equal('AsynchronousVisitor', exp.name); + }); + + }); + + describe('next()', () => { + + it('Should call promise\'s resolve as a default strategy', () => { + const resolveSpy = this.sandbox.spy(); + const exp = Asynchronous.new({}); + exp.next({}, resolveSpy); + assert.isTrue(resolveSpy.calledOnce); + }); + + }); + + describe('visit()', () => { + + it('Should visit the target', () => { + const input = Visited.new({ property: 'target', next: this.sandbox.spy() }); + const exp = Asynchronous.new(); + assert.instanceOf(exp.visit(input), Visited); + }); + + it('Should NOT visit the target: super.validate returns false', () => { + const exp = Asynchronous.new(); + assert.isNull(exp.visit()); + }); + + }); + + describe('execute()', () => { + + it('Should return a decorated target with asynchronous capabilities', () => { + const input = this.target(); + const visited = Visited.new(input); + const exp = Asynchronous.new().visit(visited); + assert.isNotNull(exp.execute); + assert.instanceOf(exp.execute(), Promise); + }); + + }); + +}); diff --git a/test/commands/visitors/commander.spec.es6 b/test/commands/visitors/commander.spec.es6 new file mode 100644 index 0000000..e69de29 diff --git a/test/commands/util/proxy/json.spec.es6 b/test/commands/visitors/formatter/json.spec.es6 similarity index 82% rename from test/commands/util/proxy/json.spec.es6 rename to test/commands/visitors/formatter/json.spec.es6 index 172f3e4..e0d1842 100644 --- a/test/commands/util/proxy/json.spec.es6 +++ b/test/commands/visitors/formatter/json.spec.es6 @@ -1,14 +1,14 @@ /** -* @module commands.util.proxy +* @module commands.visitors.formatter * @author Patricio Ferreira <3dimentionar@gmail.com> **/ import _ from 'underscore'; -import Json from 'commands/util/proxy/json'; +import Json from 'commands/visitors/formatter/json'; import Visited from 'commands/util/visitor/visited'; import Command from 'commands/command'; import InterfaceException from 'commands/util/exception/proxy/interface'; -describe('commands.util.proxy.Json', function() { +describe('commands.visitors.formatter.Json', function() { before(() => { this.sandbox = sinon.sandbox.create(); @@ -47,20 +47,22 @@ describe('commands.util.proxy.Json', function() { describe('constructor()', () => { it('Should get a new instance', () => { - assert.instanceOf(Json.new(), Json); + const exp = Json.new(); + assert.instanceOf(exp, Json); + assert.equal('JsonVisitor', exp.name); }); }); describe('visit()', () => { - it('Should visit the target visited', () => { + it('Should visit the target', () => { const input = Visited.new({ property: 'target' }); const exp = Json.new(); assert.instanceOf(exp.visit(input), Visited); }); - it('Should NOT visit the target visited', () => { + it('Should NOT visit the target', () => { const input = { property: 'target' }; const exp = Json.new(); assert.throws(() => exp.visit(input), InterfaceException.type From 413a5b8c19a8186c598babea56f6c2c2100c2536 Mon Sep 17 00:00:00 2001 From: kuakman <3dimentionar@gmail.com> Date: Mon, 13 Mar 2017 16:08:11 -0700 Subject: [PATCH 21/39] cli: checkpoint - Main folder refactor. Progress on CLI wiring. --- .babelrc | 12 ++- .gitignore | 2 - bin/sqbox | 22 +++++ {src/commands => lib}/bin/sqbox.es6 | 65 ++++++++----- {src/commands => lib}/bundle/bundle.es6 | 0 {src/commands => lib}/clean/clean.es6 | 0 {src/commands => lib}/command.es6 | 31 +++--- {src/commands => lib}/help/help.es6 | 0 {src/commands => lib}/util/adt/collection.es6 | 44 ++++----- {src/commands => lib}/util/adt/iterator.es6 | 16 ++-- .../commands => lib}/util/adt/queue-async.es6 | 20 ++-- {src/commands => lib}/util/adt/queue.es6 | 18 ++-- .../commands => lib}/util/adt/stack-async.es6 | 20 ++-- {src/commands => lib}/util/adt/stack.es6 | 12 +-- .../util/exception/adt/queue.es6 | 8 +- .../util/exception/exception.es6 | 8 +- .../util/exception/proxy/interface.es6 | 8 +- .../commands => lib}/util/factory/factory.es6 | 22 ++--- {src/commands => lib}/util/logger/logger.es6 | 46 ++++----- {src/commands => lib}/util/mixins.es6 | 2 +- .../commands => lib}/util/visitor/visited.es6 | 18 ++-- .../commands => lib}/util/visitor/visitor.es6 | 20 ++-- .../commands => lib}/visitors/async/async.es6 | 14 +-- lib/visitors/commander.es6 | 95 +++++++++++++++++++ .../visitors/configuration.es6 | 41 ++++---- .../visitors/configuration/local.es6 | 0 .../visitors/configuration/remote.es6 | 0 .../visitors/formatter/json.es6 | 10 +- .../visualize/visualize.es6 | 0 package.json | 18 ++-- release.js | 6 -- src/build/build.es6 | 38 -------- test/global.js | 2 +- test/{commands => lib}/bin/sqbox.spec.es6 | 6 +- test/{commands => lib}/command.spec.es6 | 6 +- .../util/adt/collection.spec.es6 | 10 +- .../util/adt/iterator.spec.es6 | 6 +- .../util/adt/queue-async.spec.es6 | 16 ++-- .../{commands => lib}/util/adt/queue.spec.es6 | 10 +- .../util/adt/stack-async.spec.es6 | 16 ++-- .../{commands => lib}/util/adt/stack.spec.es6 | 8 +- .../util/factory/factory.spec.es6 | 18 ++-- .../util/logger/logger.spec.es6 | 6 +- test/{commands => lib}/util/mixins.spec.es6 | 6 +- .../util/visitor/visited.spec.es6 | 10 +- .../util/visitor/visitor.spec.es6 | 12 +-- .../visitors/async/async.spec.es6 | 10 +- test/lib/visitors/commander.spec.es6 | 0 .../visitors/formatter/json.spec.es6 | 14 +-- 49 files changed, 427 insertions(+), 345 deletions(-) create mode 100644 bin/sqbox rename {src/commands => lib}/bin/sqbox.es6 (57%) rename {src/commands => lib}/bundle/bundle.es6 (100%) rename {src/commands => lib}/clean/clean.es6 (100%) rename {src/commands => lib}/command.es6 (83%) rename {src/commands => lib}/help/help.es6 (100%) rename {src/commands => lib}/util/adt/collection.es6 (90%) rename {src/commands => lib}/util/adt/iterator.es6 (88%) rename {src/commands => lib}/util/adt/queue-async.es6 (86%) rename {src/commands => lib}/util/adt/queue.es6 (89%) rename {src/commands => lib}/util/adt/stack-async.es6 (86%) rename {src/commands => lib}/util/adt/stack.es6 (90%) rename {src/commands => lib}/util/exception/adt/queue.es6 (78%) rename {src/commands => lib}/util/exception/exception.es6 (83%) rename {src/commands => lib}/util/exception/proxy/interface.es6 (79%) rename {src/commands => lib}/util/factory/factory.es6 (90%) rename {src/commands => lib}/util/logger/logger.es6 (83%) rename {src/commands => lib}/util/mixins.es6 (99%) rename {src/commands => lib}/util/visitor/visited.es6 (81%) rename {src/commands => lib}/util/visitor/visitor.es6 (68%) rename {src/commands => lib}/visitors/async/async.es6 (65%) create mode 100644 lib/visitors/commander.es6 rename src/commands/visitors/commander.es6 => lib/visitors/configuration.es6 (58%) rename src/commands/visitors/configuration/remote.es6 => lib/visitors/configuration/local.es6 (100%) rename src/commands/visualize/visualize.es6 => lib/visitors/configuration/remote.es6 (100%) rename {src/commands => lib}/visitors/formatter/json.es6 (81%) rename test/commands/visitors/commander.spec.es6 => lib/visualize/visualize.es6 (100%) delete mode 100644 release.js delete mode 100644 src/build/build.es6 rename test/{commands => lib}/bin/sqbox.spec.es6 (80%) rename test/{commands => lib}/command.spec.es6 (85%) rename test/{commands => lib}/util/adt/collection.spec.es6 (98%) rename test/{commands => lib}/util/adt/iterator.spec.es6 (95%) rename test/{commands => lib}/util/adt/queue-async.spec.es6 (80%) rename test/{commands => lib}/util/adt/queue.spec.es6 (94%) rename test/{commands => lib}/util/adt/stack-async.spec.es6 (79%) rename test/{commands => lib}/util/adt/stack.spec.es6 (94%) rename test/{commands => lib}/util/factory/factory.spec.es6 (94%) rename test/{commands => lib}/util/logger/logger.spec.es6 (97%) rename test/{commands => lib}/util/mixins.spec.es6 (96%) rename test/{commands => lib}/util/visitor/visited.spec.es6 (93%) rename test/{commands => lib}/util/visitor/visitor.spec.es6 (82%) rename test/{commands => lib}/visitors/async/async.spec.es6 (85%) create mode 100644 test/lib/visitors/commander.spec.es6 rename test/{commands => lib}/visitors/formatter/json.spec.es6 (80%) diff --git a/.babelrc b/.babelrc index 3477908..9aeec14 100644 --- a/.babelrc +++ b/.babelrc @@ -2,8 +2,16 @@ "presets": ["es2015", "stage-2"], "plugins": [ ["module-resolver", { - "root": ["./src"], - "alias": { "commands": "./commands" } + "root": ["./lib"], + "alias": { + "bin": "bin", + "bundle": "bundle", + "clean": "clean", + "help": "help", + "util": "util", + "visitors": "visitors", + "visualize": "visualize" + } }] ], "env": { diff --git a/.gitignore b/.gitignore index 033cbf2..a0d231d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,4 @@ # Project -/bin -lib .nyc_output coverage node_modules diff --git a/bin/sqbox b/bin/sqbox new file mode 100644 index 0000000..4a3f017 --- /dev/null +++ b/bin/sqbox @@ -0,0 +1,22 @@ +#!/usr/bin/env node + +require('babel-register')({ + "presets": ["es2015", "stage-2"], + "plugins": [ + ["module-resolver", { + "root": ["../"], + "cwd": __dirname, + "alias": { + "bin": "bin", + "bundle": "bundle", + "clean": "clean", + "help": "help", + "util": "util", + "visitors": "visitors", + "visualize": "visualize" + } + }] + ] +}); +require('babel-polyfill'); +require('commands/bin/sqbox').run(__dirname); diff --git a/src/commands/bin/sqbox.es6 b/lib/bin/sqbox.es6 similarity index 57% rename from src/commands/bin/sqbox.es6 rename to lib/bin/sqbox.es6 index c03f7f4..f24997f 100644 --- a/src/commands/bin/sqbox.es6 +++ b/lib/bin/sqbox.es6 @@ -1,20 +1,20 @@ /** -* @module commands.bin +* @module bin * @author Patricio Ferreira <3dimentionar@gmail.com> **/ -import 'commands/util/mixins'; +import 'util/mixins'; import extend from 'extend'; -import Factory from 'commands/util/factory/factory'; -import Command from 'commands/command'; +import Factory from 'util/factory/factory'; +import Command from 'command'; let enforcer = Symbol('SquareBox'); /** * Class SquareBox -* @extends {commands.Command} +* @extends {Command} * -* @uses {commands.bin.visitor.Commander} +* @uses {bin.visitor.Commander} **/ class SquareBox extends Command { @@ -22,7 +22,7 @@ class SquareBox extends Command { * Constructor * @public * @param {Object} [args = {}] - Constructor arguments - * @return {commands.bin.SquareBox} + * @return {bin.SquareBox} **/ constructor(...args) { super(...args); @@ -32,20 +32,31 @@ class SquareBox extends Command { /** * Attaches Events * @public - * @return {commands.bin.SquareBox} + * @return {bin.SquareBox} **/ attachEvents() { - // TODO + this.on(SquareBox.events.done, this.after); return this; } /** - * Read all command arguments using {commands.bin.visitor.Commander} + * Before Run + * @public + * @override + * @return {bin.SquareBox} + **/ + before() { + return super.before().parse(); + } + + /** + * Run * @public - * @async - * @return {commands.bin.SquareBox} + * @override + * @return {bin.SquareBox} **/ - read() { + run() { + this.before(); // TODO return this; } @@ -55,7 +66,7 @@ class SquareBox extends Command { * @public * @throws {Error} Private violation * @param {Symbol} pte - constructor enforcer - * @return {commands.bin.SquareBox} + * @return {bin.SquareBox} **/ isPrivate(pte) { if(!_.isEqual(pte, enforcer)) throw new Error('Private Violation'); @@ -66,7 +77,7 @@ class SquareBox extends Command { * Register Command Factories * @public * @override - * @return {commands.bin.SquareBox} + * @return {bin.SquareBox} **/ register() { super.register(); @@ -80,10 +91,10 @@ class SquareBox extends Command { * @type {Array} **/ static commands = [ - 'help/help', - 'clean/clean', - 'bundle/bundle', - 'visualize/visualize' + { help: 'help/help' }, + { clean: 'clean/clean' }, + { bundle: 'bundle/bundle' }, + { visualize: 'visualize/visualize' } ]; /** @@ -93,14 +104,15 @@ class SquareBox extends Command { * @type {Array} **/ static visitors = [ - 'visitors/commander' + 'visitors/commander', + 'visitors/configuration' ].concat(Command.visitors); /** * Static enforcer validation * @static - * @param {commands.bin.SquareBox} instance - squarebox instance reference - * @return {commands.bin.SquareBox} + * @param {bin.SquareBox} instance - squarebox instance reference + * @return {bin.SquareBox} **/ static isPrivate(instance) { return instance.isPrivate(enforcer); @@ -109,12 +121,13 @@ class SquareBox extends Command { /** * Static Run * @static - * @return commands.bin.SquareBox + * @param {String} [cwd = process.cwd()] - base path + * @return {bin.SquareBox} **/ - static run() { - return this.new({ cwd: process.cwd() }).read(); + static run(cwd) { + return this.new({ cwd }).read(); } } -export default SquareBox.run(); +export default SquareBox; diff --git a/src/commands/bundle/bundle.es6 b/lib/bundle/bundle.es6 similarity index 100% rename from src/commands/bundle/bundle.es6 rename to lib/bundle/bundle.es6 diff --git a/src/commands/clean/clean.es6 b/lib/clean/clean.es6 similarity index 100% rename from src/commands/clean/clean.es6 rename to lib/clean/clean.es6 diff --git a/src/commands/command.es6 b/lib/command.es6 similarity index 83% rename from src/commands/command.es6 rename to lib/command.es6 index 4620a42..6c164b2 100644 --- a/src/commands/command.es6 +++ b/lib/command.es6 @@ -1,19 +1,19 @@ /** -* @module commands +* @module * @author Patricio Ferreira <3dimentionar@gmail.com> **/ import { EventEmitter } from 'events'; -import _ from './util/mixins'; +import _ from 'util/mixins'; import extend from 'extend'; -import Factory from 'commands/util/factory/factory'; -import Visited from 'commands/util/visitor/visited'; +import Factory from 'util/factory/factory'; +import Visited from 'util/visitor/visited'; /** * Class Command -* @extends {commands.util.visitor.Visited} +* @extends {util.visitor.Visited} * -* @uses {commands.visitors.formatter.Json} -* @uses {commands.visitors.async.Asynchronous} +* @uses {visitors.formatter.Json} +* @uses {visitors.async.Asynchronous} **/ class Command extends Visited { @@ -21,7 +21,7 @@ class Command extends Visited { * Constructor * @public * @param {Object} [args = {}] - constructor arguments - * @return {commands.Command} + * @return {Command} **/ constructor(args = {}) { super(); @@ -30,18 +30,19 @@ class Command extends Visited { /** * Set settings + * @see {visitors.Configuration} * @public * @param {Object} [options = {}] - command options - * @return {commands.Command} + * @return {Command} **/ settings(options) { - return extend(true, this, Command.defaults, _.pick(options, Command.options)); + return extend(true, this, this.constructor.defaults, _.pick(options, Command.options)); } /** * Registers Visitors * @public - * @return {commands.Command} + * @return {Command} **/ register() { Factory.basePath(this.dirname).registerAll(this.constructor.visitors); @@ -62,7 +63,7 @@ class Command extends Visited { * FIXME: Implement when the command gets rejected. * @public * @override - * @param adt {commands.util.proxy.Asynchronous} adt used for asynchronous operations + * @param adt {util.proxy.Asynchronous} adt used for asynchronous operations * @param resolve {Function} asynchronous promise's resolve * @param reject {Function} asynchronous promise's reject * @return {Promise} @@ -75,7 +76,7 @@ class Command extends Visited { /** * Default hook before run * @public - * @return {commands.Command} + * @return {Command} **/ before() { return this.pending(); @@ -84,7 +85,7 @@ class Command extends Visited { /** * Default Run * @public - * @return {commands.Command} + * @return {Command} **/ run() { return this.before().after(); @@ -93,7 +94,7 @@ class Command extends Visited { /** * Default hook after run * @public - * @return {commands.Command} + * @return {Command} **/ after() { return this.done(); diff --git a/src/commands/help/help.es6 b/lib/help/help.es6 similarity index 100% rename from src/commands/help/help.es6 rename to lib/help/help.es6 diff --git a/src/commands/util/adt/collection.es6 b/lib/util/adt/collection.es6 similarity index 90% rename from src/commands/util/adt/collection.es6 rename to lib/util/adt/collection.es6 index dcbfc48..1beed2c 100644 --- a/src/commands/util/adt/collection.es6 +++ b/lib/util/adt/collection.es6 @@ -1,11 +1,11 @@ /** -* @module commands.util.adt +* @module util.adt * @author Patricio Ferreira <3dimentionar@gmail.com> **/ import { EventEmitter } from 'events'; import _ from 'underscore'; import extend from 'extend'; -import Iterator from './iterator'; +import Iterator from 'util/adt/iterator'; /** * Class Collection @@ -18,7 +18,7 @@ class Collection extends EventEmitter { * @public * @param [initial = []] {Array} Initial Array * @param [opts = {}] {Object} collection options - * @return {commands.util.adt.Collection} + * @return {util.adt.Collection} **/ constructor(initial = [], opts = {}) { super(); @@ -43,7 +43,7 @@ class Collection extends EventEmitter { * @param name {String} event name * @param [opts = {}] {Object} additional options * @param [...args] {Any} additional arguments to pass to the emit - * @return {commands.util.adt.Collection} + * @return {util.adt.Collection} **/ _fire(name, opts, ...args) { if(!opts.silent) this.emit(name, this, ...args); @@ -65,7 +65,7 @@ class Collection extends EventEmitter { /** * Returns a json representation of a given element only if this collection has defined an interface - * and the given element implements {commands.util.proxy.Json} interface + * and the given element implements {util.proxy.Json} interface * @private * @param element {Any} element to get json representation * @return {Any} @@ -93,7 +93,7 @@ class Collection extends EventEmitter { * @fires {Collection.events.set} * @param col {Array} collection of new elements * @param [opts = {}] {Object} additional options - * @return {commands.util.adt.Collection} + * @return {util.adt.Collection} **/ set(col, opts = {}) { if(!this._valid(col) || !_.isArray(col)) return this; @@ -124,7 +124,7 @@ class Collection extends EventEmitter { * @fires {Collection.events.addall} * @param [col = []] {Array} collection of new elements to add * @param [opts = {}] {Object} additional options - * @return {commands.util.adt.Collection} + * @return {util.adt.Collection} **/ addAll(col = [], opts = {}) { if(!_.isArray(col) || col.length === 0) return this; @@ -213,7 +213,7 @@ class Collection extends EventEmitter { * @fires {Collection.events.removeall} * @param [col = []] {Array} collection of elements to remove * @param [opts = {}] {Object} additional options - * @return {commands.util.adt.Collection} + * @return {util.adt.Collection} **/ removeAll(col = [], opts = {}) { if(!_.isArray(col) || col.length === 0) return this; @@ -227,7 +227,7 @@ class Collection extends EventEmitter { * @fires {Collection.events.remove} * @param predicate {Function} predicate used to evaluate * @param [opts = {}] {Object} additional options - * @return {commands.util.adt.Collection} + * @return {util.adt.Collection} **/ removeBy(predicate, opts = {}) { if(!this._valid(predicate) || !_.isFunction(predicate)) return this; @@ -246,7 +246,7 @@ class Collection extends EventEmitter { * @fires {Collection.events.sort} * @param [opts = {}] {Object} additional options * @param comparator {Function} comparator reference - * @return {commands.util.adt.Collection} + * @return {util.adt.Collection} **/ sort(comparator, opts = {}) { (!_.defined(comparator) || !_.isFunction(comparator)) ? @@ -258,7 +258,7 @@ class Collection extends EventEmitter { /** * Returns a new iterator instance of this collection * @public - * @return {commands.util.adt.Iterator} + * @return {util.adt.Iterator} **/ iterator() { return Iterator.new(this._collection); @@ -269,7 +269,7 @@ class Collection extends EventEmitter { * @public * @fires {Collection.events.reset} * @param [opts = {}] {Object} additional options - * @return {commands.util.adt.Collection} + * @return {util.adt.Collection} **/ reset(opts = {}) { this._collection = []; @@ -321,37 +321,37 @@ class Collection extends EventEmitter { /** * @event set **/ - set: 'commands:util:adt:collection:set', + set: 'util:adt:collection:set', /** * @event add **/ - add: 'commands:util:adt:collection:add', + add: 'util:adt:collection:add', /** * @event addall **/ - addall: 'commands:util:adt:collection:addall', + addall: 'util:adt:collection:addall', /** * @event remove **/ - remove: 'commands:util:adt:collection:remove', + remove: 'util:adt:collection:remove', /** * @event removeall **/ - removeall: 'commands:util:adt:collection:removeall', + removeall: 'util:adt:collection:removeall', /** * @event reset **/ - reset: 'commands:util:adt:collection:reset', + reset: 'util:adt:collection:reset', /** * @event sort **/ - sort: 'commands:util:adt:collection:sort' + sort: 'util:adt:collection:sort' }; /** @@ -398,8 +398,8 @@ class Collection extends EventEmitter { /** * Underscore aggregation * @static - * @param instance {commands.util.adt.Collection} - * @return {commands.util.adt.Collection} + * @param instance {util.adt.Collection} + * @return {util.adt.Collection} **/ static _aggregate() { _.each(this.UNDERSCORE(), function(method) { @@ -415,7 +415,7 @@ class Collection extends EventEmitter { * Static constructor * @static * @param [...args] {Any} Constructor arguments - * @return {commands.util.adt.Collection} + * @return {util.adt.Collection} **/ static new(...args) { return new this(...args); diff --git a/src/commands/util/adt/iterator.es6 b/lib/util/adt/iterator.es6 similarity index 88% rename from src/commands/util/adt/iterator.es6 rename to lib/util/adt/iterator.es6 index 6b777f2..1c73528 100644 --- a/src/commands/util/adt/iterator.es6 +++ b/lib/util/adt/iterator.es6 @@ -1,5 +1,5 @@ /** -* @module commands.util.adt +* @module util.adt * @author Patricio Ferreira <3dimentionar@gmail.com> **/ import { EventEmitter } from 'events'; @@ -29,7 +29,7 @@ class Iterator extends EventEmitter { * Constructor * @public * @param [initial = []] {Array} initial elements in the collection - * @return {commands.util.adt.Iterator} + * @return {util.adt.Iterator} **/ constructor(initial = []) { super(); @@ -53,7 +53,7 @@ class Iterator extends EventEmitter { * @public * @param [col = []] {Array} elements to set to this iterator * @param [opts = {}] {Object} additional options - * @return {commands.util.adt.Iterator} + * @return {util.adt.Iterator} **/ set(col = [], opts = {}) { if(!this._valid(col) || col.length === 0) return this; @@ -82,7 +82,7 @@ class Iterator extends EventEmitter { /** * Reset the cursor to the beginning to the index 0 * @public - * @return {commands.util.adt.Iterator} + * @return {util.adt.Iterator} **/ rewind() { this._pointer = 0; @@ -94,7 +94,7 @@ class Iterator extends EventEmitter { * @public * @fires {Iterator.events.remove} * @param [opts = {}] {Object} additional options - * @return {commands.util.adt.Iterator} + * @return {util.adt.Iterator} **/ remove(opts = {}) { if(this._collection.length === 0) return this; @@ -111,19 +111,19 @@ class Iterator extends EventEmitter { /** * @event set **/ - set: 'commands:util:adt:iterator:set', + set: 'util:adt:iterator:set', /** * @event remove **/ - remove: 'commands:util:adt:iterator:remove' + remove: 'util:adt:iterator:remove' } /** * Static Constructor * @static * @param [...args] {Any} constructor arguments - * @return {commands.util.adt.Iterator} + * @return {util.adt.Iterator} **/ static new(...args) { return new this(...args); diff --git a/src/commands/util/adt/queue-async.es6 b/lib/util/adt/queue-async.es6 similarity index 86% rename from src/commands/util/adt/queue-async.es6 rename to lib/util/adt/queue-async.es6 index 64f7457..25daf52 100644 --- a/src/commands/util/adt/queue-async.es6 +++ b/lib/util/adt/queue-async.es6 @@ -1,17 +1,17 @@ /** -* @module commands.util.adt +* @module util.adt * @author Patricio Ferreira <3dimentionar@gmail.com> **/ import _ from 'underscore'; import extend from 'extend'; -import Queue from 'commands/util/adt/queue'; -import Asynchronous from 'commands/visitors/async/async'; +import Queue from 'util/adt/queue'; +import Asynchronous from 'visitors/async/async'; /** * Class QueueAsync * Defines the interface of an asynchronous FIFO Queue (FirstIn-FirstOut). * Interface for objects on this queue, **must** implement method `next` of -* {@link commands.visitors.async.Asynchronous} for promise resolution. +* {@link visitors.async.Asynchronous} for promise resolution. * @example *
Usage
* @@ -19,7 +19,7 @@ import Asynchronous from 'commands/visitors/async/async'; * .on(QueueAsync.events.next, (element) => console.log('Next: ', element)) * .on(QueueAsync.events.end, (results) => console.log('End: ', results)) * const mypromise = p.poll(); // asynchronous -* @extends commands.util.adt.Queue +* @extends util.adt.Queue **/ class QueueAsync extends Queue { @@ -29,7 +29,7 @@ class QueueAsync extends Queue { * @override * @param {Array} [initial = []] - initial elements * @param {Object} [opts = {}] - collection options - * @return {commands.util.adt.QueueAsync} + * @return {util.adt.QueueAsync} **/ constructor(initial = [], opts = {}) { return super(initial, extend(true, opts, { _visitor: Asynchronous.new(), _last: [] })); @@ -52,7 +52,7 @@ class QueueAsync extends Queue { /** * Resets Last Results * @public - * @return {commands.util.adt.QueueAsync} + * @return {util.adt.QueueAsync} **/ _resetLast() { this._last = []; @@ -102,7 +102,7 @@ class QueueAsync extends Queue { * @public * @emits {QueueAsync.events.end} - when opts.silent is false or undefined * @param {Object} [opts = {}] - additional options - * @return {commands.util.adt.QueueAsync} + * @return {util.adt.QueueAsync} **/ end(opts) { if(!opts.silent) this.emit(QueueAsync.events.end, this._last); @@ -119,12 +119,12 @@ class QueueAsync extends Queue { /** * @event next **/ - next: 'commands:util:adt:queue-async:next', + next: 'util:adt:queue-async:next', /** * @event end **/ - end: 'commands:util:adt:queue-async:end' + end: 'util:adt:queue-async:end' }); diff --git a/src/commands/util/adt/queue.es6 b/lib/util/adt/queue.es6 similarity index 89% rename from src/commands/util/adt/queue.es6 rename to lib/util/adt/queue.es6 index eb11ae0..2a785f8 100644 --- a/src/commands/util/adt/queue.es6 +++ b/lib/util/adt/queue.es6 @@ -1,11 +1,11 @@ /** -* @module commands.util.adt +* @module util.adt * @author Patricio Ferreira <3dimentionar@gmail.com> **/ import _ from 'underscore'; import extend from 'extend'; -import Collection from 'commands/util/adt/collection'; -import QueueException from 'commands/util/exception/adt/queue'; +import Collection from 'util/adt/collection'; +import QueueException from 'util/exception/adt/queue'; /** * Class Queue @@ -20,7 +20,7 @@ import QueueException from 'commands/util/exception/adt/queue'; * let myqueue = new Queue([{ name: 'one' }, { name: 'two' }], { capacity: 3, interface: MyClass }); * myqueue.offer({ name: 3 }); // Adds one more element without violating capacity (3) * myqueue.poll(); -* @extends commands.util.adt.Collection +* @extends util.adt.Collection **/ class Queue extends Collection { @@ -30,7 +30,7 @@ class Queue extends Collection { * @override * @param [initial = []] {Array} Initial Array * @param [opts = {}] {Object} collection options - * @return {commands.util.adt.Queue} + * @return {util.adt.Queue} **/ constructor(initial = [], opts = {}) { const { capacity } = opts; @@ -41,7 +41,7 @@ class Queue extends Collection { * Validates element or array of elements to decide either, to add or not the elements on this queue * @private * @override - * @throws {commands.util.exceptions.QueueException} + * @throws {util.exceptions.QueueException} * @param element {Any} element to validate * @return {Boolean} **/ @@ -67,7 +67,7 @@ class Queue extends Collection { * @override * @param [col = []] {Array} collection of elements * @param [opts = {}] {Object} additional options - * @return {commands.util.adt.Queue} + * @return {util.adt.Queue} **/ set(col = [], opts = {}) { if(!this._validCapacity() || !this._valid(col) || !_.isArray(col) || col.length === 0) return this; @@ -120,12 +120,12 @@ class Queue extends Collection { /** * @event offer **/ - offer: 'commands:util:adt:queue:offer', + offer: 'util:adt:queue:offer', /** * @event poll **/ - poll: 'commands:util:adt:queue:poll' + poll: 'util:adt:queue:poll' }); } diff --git a/src/commands/util/adt/stack-async.es6 b/lib/util/adt/stack-async.es6 similarity index 86% rename from src/commands/util/adt/stack-async.es6 rename to lib/util/adt/stack-async.es6 index 4bebd23..a714504 100644 --- a/src/commands/util/adt/stack-async.es6 +++ b/lib/util/adt/stack-async.es6 @@ -1,16 +1,16 @@ /** -* @module commands.util.adt +* @module util.adt * @author Patricio Ferreira <3dimentionar@gmail.com> **/ import _ from 'underscore'; import extend from 'extend'; -import Stack from 'commands/util/adt/stack'; -import Asynchronous from 'commands/visitors/async/async'; +import Stack from 'util/adt/stack'; +import Asynchronous from 'visitors/async/async'; /** * Class StackAsync * Defines the interface of a Stack LIFO (LastIn-FirstOut) -* Interface for objects on this stack, must implement method `next` of {@link commands.util.proxy.Asynchronous} +* Interface for objects on this stack, must implement method `next` of {@link util.proxy.Asynchronous} * for promise resolution. * @example *
Usage
@@ -19,7 +19,7 @@ import Asynchronous from 'commands/visitors/async/async'; * .on(StackAsync.events.next, (element) => console.log('Next: ', element)) * .on(StackAsync.events.end, (results) => console.log('End: ', results)) * const mypromise = mystack.pop(); // asynchronous -* @extends commands.util.adt.Stack +* @extends util.adt.Stack **/ class StackAsync extends Stack { @@ -29,7 +29,7 @@ class StackAsync extends Stack { * @override * @param [initial = []] {Array} Initial Array * @param [opts = {}] {Object} collection options - * @return {commands.util.adt.StackAsync} + * @return {util.adt.StackAsync} **/ constructor(initial = [], opts = {}) { return super(initial, extend(true, opts, { _visitor: Asynchronous.new(), _last: [] })); @@ -52,7 +52,7 @@ class StackAsync extends Stack { /** * Resets Last Results * @public - * @return {commands.util.adt.StackAsync} + * @return {util.adt.StackAsync} **/ _resetLast() { this._last = []; @@ -102,7 +102,7 @@ class StackAsync extends Stack { * @public * @emits {StackAsync.events.end} - when opts.silent is false or undefined * @param [opts = {}] {Object} additional options - * @return {commands.util.adt.StackAsync} + * @return {util.adt.StackAsync} **/ end(opts) { if(!opts.silent) this.emit(StackAsync.events.end, this._last); @@ -119,12 +119,12 @@ class StackAsync extends Stack { /** * @event next **/ - next: 'commands:util:adt:stack-async:next', + next: 'util:adt:stack-async:next', /** * @event end **/ - end: 'commands:util:adt:stack-async:end' + end: 'util:adt:stack-async:end' }); diff --git a/src/commands/util/adt/stack.es6 b/lib/util/adt/stack.es6 similarity index 90% rename from src/commands/util/adt/stack.es6 rename to lib/util/adt/stack.es6 index 3ff7d3c..6267d04 100644 --- a/src/commands/util/adt/stack.es6 +++ b/lib/util/adt/stack.es6 @@ -1,12 +1,12 @@ /** -* @module commands.util.adt.Stack +* @module util.adt.Stack * @author Patricio Ferreira <3dimentionar@gmail.com> **/ 'use strict'; import _ from 'underscore'; import extend from 'extend'; -import Collection from 'commands/util/adt/collection'; +import Collection from 'util/adt/collection'; /** * Class Stack @@ -21,7 +21,7 @@ import Collection from 'commands/util/adt/collection'; * let mystack = new Stack([{ name: 'one' }, { name: 'two' }], { interface: MyClass }); * mystack.push({ name: 3 }); // Adds one more element into the stack * mystack.pop(); // Outputs { name: 3 } of MyClass, removing the element -* @extends commands.util.adt.Collection +* @extends util.adt.Collection **/ class Stack extends Collection { @@ -30,7 +30,7 @@ class Stack extends Collection { * @public * @param [initial = []] {Array} initial collection of elements on this stack * @param [opts = {}] {Object} additional options - * @return {commands.util.adt.Stack} + * @return {util.adt.Stack} **/ constructor(initial = [], opts = {}) { super(initial, opts); @@ -92,12 +92,12 @@ class Stack extends Collection { /** * @event push **/ - push: 'commands:util:adt:stack:push', + push: 'util:adt:stack:push', /** * @event pop **/ - pop: 'commands:util:adt:stack:pop' + pop: 'util:adt:stack:pop' }); } diff --git a/src/commands/util/exception/adt/queue.es6 b/lib/util/exception/adt/queue.es6 similarity index 78% rename from src/commands/util/exception/adt/queue.es6 rename to lib/util/exception/adt/queue.es6 index 8450778..29e7364 100644 --- a/src/commands/util/exception/adt/queue.es6 +++ b/lib/util/exception/adt/queue.es6 @@ -1,14 +1,14 @@ /** -* @module commands.util.exception.adt +* @module util.exception.adt * @author Patricio Ferreira <3dimentionar@gmail.com> **/ import _ from 'underscore'; import extend from 'extend'; -import Exception from '../exception'; +import Exception from 'util/exception/exception'; /** * Class QueueException -* @extends {commands.util.exception.Exception} +* @extends {util.exception.Exception} **/ class QueueException extends Exception { @@ -17,7 +17,7 @@ class QueueException extends Exception { * @public * @override * @param [...args] {Any} constructor attribute - * @return {commands.util.exception.adt.QueueException} + * @return {util.exception.adt.QueueException} **/ constructor(...args) { super(...args); diff --git a/src/commands/util/exception/exception.es6 b/lib/util/exception/exception.es6 similarity index 83% rename from src/commands/util/exception/exception.es6 rename to lib/util/exception/exception.es6 index 0664a7a..6ce4a6d 100644 --- a/src/commands/util/exception/exception.es6 +++ b/lib/util/exception/exception.es6 @@ -1,10 +1,10 @@ /** -* @module commands.util.exception +* @module util.exception * @author Patricio Ferreira <3dimentionar@gmail.com> **/ import _ from 'underscore'; import extend from 'extend'; -import Logger from '../logger/logger'; +import Logger from 'util/logger/logger'; /** * Class Exception @@ -18,7 +18,7 @@ class Exception extends Error { * @override * @param message {Function} message template function * @param [...args] {Any} constructor attribute - * @return {commands.util.exception.Exception} + * @return {util.exception.Exception} **/ constructor(message, ...args) { super(message); @@ -41,7 +41,7 @@ class Exception extends Error { * @static * @param [type = 'unknown'] * @param [...args] {Any} additional arguments - * @return {commands.util.exception.Exception} + * @return {util.exception.Exception} **/ static new(type, ...args) { return new this(this.type[type](...args), ...args); diff --git a/src/commands/util/exception/proxy/interface.es6 b/lib/util/exception/proxy/interface.es6 similarity index 79% rename from src/commands/util/exception/proxy/interface.es6 rename to lib/util/exception/proxy/interface.es6 index fe4821c..e2cd882 100644 --- a/src/commands/util/exception/proxy/interface.es6 +++ b/lib/util/exception/proxy/interface.es6 @@ -1,14 +1,14 @@ /** -* @module commands.util.exception.proxy +* @module util.exception.proxy * @author Patricio Ferreira <3dimentionar@gmail.com> **/ import _ from 'underscore'; import extend from 'extend'; -import Exception from '../exception'; +import Exception from 'util/exception/exception'; /** * Class InterfaceException -* @extends {commands.util.exception.Exception} +* @extends {util.exception.Exception} **/ class InterfaceException extends Exception { @@ -17,7 +17,7 @@ class InterfaceException extends Exception { * @public * @override * @param [...args] {Any} constructor attribute - * @return {commands.util.exception.proxy.InterfaceException} + * @return {util.exception.proxy.InterfaceException} **/ constructor(...args) { super(...args); diff --git a/src/commands/util/factory/factory.es6 b/lib/util/factory/factory.es6 similarity index 90% rename from src/commands/util/factory/factory.es6 rename to lib/util/factory/factory.es6 index 9a032b2..5660203 100644 --- a/src/commands/util/factory/factory.es6 +++ b/lib/util/factory/factory.es6 @@ -1,13 +1,13 @@ /** -* @module commands.util.factory +* @module util.factory * @author Patricio Ferreira <3dimentionar@gmail.com> **/ -import _ from 'commands/util/mixins'; +import _ from 'util/mixins'; import fs from 'fs-extra'; import path, { resolve } from 'path'; import extend from 'extend'; import { EventEmitter } from 'events'; -import logger from 'commands/util/logger/logger'; +import logger from 'util/logger/logger'; /** * Class Factory @@ -19,7 +19,7 @@ class Factory extends EventEmitter { * Constructor * @public * @param {Object} [attrs = {}] - constructor attributes - * @return {commands.util.factory.Factory} + * @return {util.factory.Factory} **/ constructor(attrs = {}) { super(); @@ -29,7 +29,7 @@ class Factory extends EventEmitter { /** * Resets factories * @public - * @return {commands.util.factoryFactory} + * @return {util.factoryFactory} **/ reset() { this._factories.clear(); @@ -40,7 +40,7 @@ class Factory extends EventEmitter { * Sets a base path * @public * @param {String} [ph = Factory.basePath] - base path to resolve factories filepaths - * @return {commands.util.factory.Factory} + * @return {util.factory.Factory} **/ basePath(ph = Factory.basePath) { this.path = ph; @@ -115,7 +115,7 @@ class Factory extends EventEmitter { * Registers a single factory object with a given path, if it haven't been registered before. * @public * @param {String} ph - factory path location - * @return {commands.util.factory.Factory} + * @return {util.factory.Factory} **/ register(ph) { if(this._validate(ph) && !this.exists(ph)) this._factories.set(ph, this._resolve(ph)); @@ -126,7 +126,7 @@ class Factory extends EventEmitter { * Registers a list of factory objects with a given path list, if they haven't been registered before. * @public * @param {Array} [paths = []] - factory path locations - * @return {commands.util.factory.Factory} + * @return {util.factory.Factory} **/ registerAll(paths = []) { _.each(paths, this.register, this); @@ -137,7 +137,7 @@ class Factory extends EventEmitter { * Unregisters a single factory object with a given path, if it have been registered before. * @public * @param {String} ph - factory path location - * @return {commands.util.factory.Factory} + * @return {util.factory.Factory} **/ unregister(ph) { if(this.exists(ph)) this._factories.delete(ph); @@ -148,7 +148,7 @@ class Factory extends EventEmitter { * Unregisters a list of factory objects with a given path list, if they have been registered before. * @public * @param {Array} [paths = []] - factory path locations - * @return {commands.util.factory.Factory} + * @return {util.factory.Factory} **/ unregisterAll(paths = []) { _.each(paths, this.unregister, this); @@ -188,7 +188,7 @@ class Factory extends EventEmitter { * Static Constructor * @static * @param {Any} [...args] - constructor arguments - * @return {commands.util.factory.Factory} + * @return {util.factory.Factory} **/ static new(...args) { return new this(...args); diff --git a/src/commands/util/logger/logger.es6 b/lib/util/logger/logger.es6 similarity index 83% rename from src/commands/util/logger/logger.es6 rename to lib/util/logger/logger.es6 index dfc582e..f48af1e 100644 --- a/src/commands/util/logger/logger.es6 +++ b/lib/util/logger/logger.es6 @@ -1,5 +1,5 @@ /** -* @module commands.util.logger +* @module util.logger * @author Patricio Ferreira <3dimentionar@gmail.com> **/ import { EventEmitter } from 'events'; @@ -17,7 +17,7 @@ export class Logger extends EventEmitter { /** * Constructor * @public - * @return {commands.util.logger.Logger} + * @return {util.logger.Logger} **/ constructor() { super(); @@ -38,9 +38,9 @@ export class Logger extends EventEmitter { /** * Proxy's 'get' trap override * @public - * @param {commands.util.logger.Logger} target - logger instance reference + * @param {util.logger.Logger} target - logger instance reference * @param {String} property - property to resolve - * @return {commands.util.logger.Logger} + * @return {util.logger.Logger} **/ get(target, property) { if(_.defined(this[property])) return this[property]; @@ -51,7 +51,7 @@ export class Logger extends EventEmitter { /** * Proxy's 'set' trap override * @public - * @param {commands.util.logger.Logger} target - logger instance reference + * @param {util.logger.Logger} target - logger instance reference * @param {String} property - property to resolve * @param {Any} value - value to set * @return {Boolean} @@ -64,7 +64,7 @@ export class Logger extends EventEmitter { /** * Proxy's 'defineProperty' trap override * @public - * @param {commands.util.logger.Logger} target - logger instance reference + * @param {util.logger.Logger} target - logger instance reference * @param {String} property - property to define * @return {Boolean} **/ @@ -76,7 +76,7 @@ export class Logger extends EventEmitter { /** * Proxy's 'deleteProperty' trap override * @public - * @param {commands.util.logger.Logger} target - logger instance reference + * @param {util.logger.Logger} target - logger instance reference * @param {String} property - property to delete * @return {Boolean} **/ @@ -88,7 +88,7 @@ export class Logger extends EventEmitter { /** * Proxy's 'apply' trap override * @private - * @param {commands.util.logger.Logger} target - logger instance reference + * @param {util.logger.Logger} target - logger instance reference * @param {Any} thisArg - this context reference * @param {Array} args - arguments list * @return @@ -120,7 +120,7 @@ export class Logger extends EventEmitter { * Appends Message to the internal buffer * @public * @param {String} message - message to add - * @return {commands.util.logger.Logger} + * @return {util.logger.Logger} **/ _add(message) { this._buffer.push(message); @@ -131,7 +131,7 @@ export class Logger extends EventEmitter { * Flushes message buffer * @private * @emits {Logger.events.flush} - * @return {commands.util.logger.Logger} + * @return {util.logger.Logger} **/ _flush() { this._buffer = []; @@ -143,7 +143,7 @@ export class Logger extends EventEmitter { * @public * @param {String} name - event name * @param opts {Object} - additional options - * @return {commands.util.logger.Logger} + * @return {util.logger.Logger} **/ _fire(name, opts) { if(!opts.silent) this.emit(name, this); @@ -155,7 +155,7 @@ export class Logger extends EventEmitter { * @private * @param {Function} style - chalk style override * @param {Object} type - Logger type - * @return {commands.util.logger.Logger} + * @return {util.logger.Logger} **/ _output(style, type) { if(!this._validate(type)) return this._flush(); @@ -167,7 +167,7 @@ export class Logger extends EventEmitter { * Standard Out (console.log wrapper) * @private * @param {String} content - content to output in stdout - * @return {commands.util.logger.Logger} + * @return {util.logger.Logger} **/ _stdout(content) { console.warn(content); @@ -180,7 +180,7 @@ export class Logger extends EventEmitter { * @emits {Logger.events.debug} * @param {Object} [opts = {}] - additional options * @param {Object} style - chalk optional style - * @return {commands.util.logger.Logger} + * @return {util.logger.Logger} **/ out(opts = {}, style) { return this._output(style, Logger.type.output)._fire(Logger.events.output, opts); @@ -192,7 +192,7 @@ export class Logger extends EventEmitter { * @emits {Logger.events.debug} * @param {Object} [opts = {}] - additional options * @param {Object} style - chalk optional style - * @return {commands.util.logger.Logger} + * @return {util.logger.Logger} **/ debug(opts = {}, style) { return this._output(style, Logger.type.debug)._fire(Logger.events.debug, opts); @@ -203,7 +203,7 @@ export class Logger extends EventEmitter { * @public * @emits {Logger.events.warning} * @param {Object} [opts = {}] - additional options - * @return {commands.util.logger.Logger} + * @return {util.logger.Logger} **/ warn(opts = {}) { return this._output(null, Logger.type.warning)._fire(Logger.events.warning, opts); @@ -276,27 +276,27 @@ export class Logger extends EventEmitter { /** * @event flush **/ - flush: 'commands:util:logger:flush', + flush: 'util:logger:flush', /** * @event debug **/ - debug: 'commands:util:logger:debug', + debug: 'util:logger:debug', /** * @event output **/ - output: 'commands:util:logger:output', + output: 'util:logger:output', /** * @event warning **/ - warning: 'commands:util:logger:warning', + warning: 'util:logger:warning', /** * @event fatal **/ - fatal:'commands:util:logger:fatal' + fatal:'util:logger:fatal' } @@ -304,7 +304,7 @@ export class Logger extends EventEmitter { * Sets an static reference of an instance of this class * @private * @static - * @param {commands.util.logger.Logger} logger - logger instance reference + * @param {util.logger.Logger} logger - logger instance reference * @return {Any} **/ static _instance(logger) { @@ -315,7 +315,7 @@ export class Logger extends EventEmitter { * Static Constructor * @static * @param {Any} [...args] - constructor arguments - * @return {commands.util.logger.Logger} + * @return {util.logger.Logger} **/ static new(...args) { return new this(...args); diff --git a/src/commands/util/mixins.es6 b/lib/util/mixins.es6 similarity index 99% rename from src/commands/util/mixins.es6 rename to lib/util/mixins.es6 index a565879..9aad2bf 100644 --- a/src/commands/util/mixins.es6 +++ b/lib/util/mixins.es6 @@ -1,5 +1,5 @@ /** -* Underscore Mixins +* @module util * @author Patricio Ferreira <3dimentionar@gmail.com> **/ import _ from 'underscore'; diff --git a/src/commands/util/visitor/visited.es6 b/lib/util/visitor/visited.es6 similarity index 81% rename from src/commands/util/visitor/visited.es6 rename to lib/util/visitor/visited.es6 index 57abcc6..86dd6d6 100644 --- a/src/commands/util/visitor/visited.es6 +++ b/lib/util/visitor/visited.es6 @@ -1,12 +1,12 @@ /** -* @module commands.util.visitor +* @module util.visitor * @author Patricio Ferreira <3dimentionar@gmail.com> **/ import { EventEmitter } from 'events'; import _ from 'underscore'; import extend from 'extend'; -import Visitor from 'commands/util/visitor/visitor'; -import InterfaceException from 'commands/util/exception/proxy/interface'; +import Visitor from 'util/visitor/visitor'; +import InterfaceException from 'util/exception/proxy/interface'; /** * Class Visited @@ -18,7 +18,7 @@ class Visited extends EventEmitter { * Constructor * @public * @param {Any} [...args] - constructor arguments - * @return {commands.util.visitor.Visited} + * @return {util.visitor.Visited} **/ constructor(...args) { super(); @@ -71,9 +71,9 @@ class Visited extends EventEmitter { } /** - * Returns true if a given visitor is defined and an instance of commands.util.visitor.Visitor, false otherwise + * Returns true if a given visitor is defined and an instance of util.visitor.Visitor, false otherwise * @public - * @param {commands.util.visitor.Visitor} visitor - visitor to validate + * @param {util.visitor.Visitor} visitor - visitor to validate * @return {Boolean} **/ validate(visitor) { @@ -83,8 +83,8 @@ class Visited extends EventEmitter { /** * Default strategy that accepts visitor by this visited instance * @public - * @param {commands.util.visitor.Visitor} vi - visitor to accept - * @return {commands.util.visitor.Visited} + * @param {util.visitor.Visitor} vi - visitor to accept + * @return {util.visitor.Visited} **/ accept(visitor) { return this.validate(visitor) ? visitor.visit(this) : this; @@ -94,7 +94,7 @@ class Visited extends EventEmitter { * Static Constructor * @static * @param {Any} [...args] - constructor arguments - * @return {commands.util.visitor.Visited} + * @return {util.visitor.Visited} **/ static new(...args) { return new this(...args); diff --git a/src/commands/util/visitor/visitor.es6 b/lib/util/visitor/visitor.es6 similarity index 68% rename from src/commands/util/visitor/visitor.es6 rename to lib/util/visitor/visitor.es6 index 2003dd3..d648cfa 100644 --- a/src/commands/util/visitor/visitor.es6 +++ b/lib/util/visitor/visitor.es6 @@ -1,12 +1,12 @@ /** -* @module commands.util.visitor +* @module util.visitor * @author Patricio Ferreira <3dimentionar@gmail.com> **/ import { EventEmitter } from 'events'; import _ from 'underscore'; import extend from 'extend'; -import Visited from 'commands/util/visitor/visited'; -import InterfaceException from 'commands/util/exception/proxy/interface'; +import Visited from 'util/visitor/visited'; +import InterfaceException from 'util/exception/proxy/interface'; /** * Class Visitor @@ -18,7 +18,7 @@ class Visitor extends EventEmitter { * Constructor * @public * @param {Any} [...args] - constructor arguments - * @return {commands.util.visitor.Visitor} + * @return {util.visitor.Visitor} **/ constructor(...args) { super(); @@ -29,14 +29,14 @@ class Visitor extends EventEmitter { * Returns true if the visited instance implements Visited interface. * Otherwise, this method will raise an InterfaceException. * @public - * @throws {commands.util.exception.proxy.InterfaceException} - * @param {commands.util.visitor.Visited} vi - visited instance + * @throws {util.exception.proxy.InterfaceException} + * @param {util.visitor.Visited} vi - visited instance * @return {Boolean} **/ validate(vi) { if(!_.defined(vi)) return false; if(!_.defined(vi.accept)) - throw InterfaceException.new('interface', { name: 'commands.util.visitor.Visited' }); + throw InterfaceException.new('interface', { name: 'util.visitor.Visited' }); return true; } @@ -44,9 +44,9 @@ class Visitor extends EventEmitter { * Default Visit Strategy will return the visited object verbatim. * This method is most likely to be overriden by subsclasses of this visitor. * @public - * @param {commands.util.visitor.Visited} vi - instance to be visited by this visitor + * @param {util.visitor.Visited} vi - instance to be visited by this visitor * @param {Any} [...args] - arguments passed to the vistior who visit the current visited instance - * @return {commands.util.visitor.Visited} + * @return {util.visitor.Visited} **/ visit(vi, ...args) { return this.validate(vi) ? new Proxy(this, vi) : null; @@ -65,7 +65,7 @@ class Visitor extends EventEmitter { * Static Constructor * @static * @param {Any} [...args] - constructor arguments - * @return {commands.util.visitor.Visitor} + * @return {util.visitor.Visitor} **/ static new(...args) { return new this(...args); diff --git a/src/commands/visitors/async/async.es6 b/lib/visitors/async/async.es6 similarity index 65% rename from src/commands/visitors/async/async.es6 rename to lib/visitors/async/async.es6 index 7cd3197..4b43205 100644 --- a/src/commands/visitors/async/async.es6 +++ b/lib/visitors/async/async.es6 @@ -1,23 +1,23 @@ /** -* @module commands.visitors.async +* @module visitors.async * @author Patricio Ferreira <3dimentionar@gmail.com> **/ import { EventEmitter } from 'events'; import _ from 'underscore'; -import Visitor from 'commands/util/visitor/visitor'; +import Visitor from 'util/visitor/visitor'; /** * Class Asynchronous -* @extends {commands.util.visitor.Visitor} +* @extends {util.visitor.Visitor} **/ class Asynchronous extends Visitor { /** * Default Next Strategy to always resolve the promise. * Note: This method was designed (and it's most likely) to be overriden by - * {@link commands.util.visitor.Visited} subclasses that use this visitor. + * {@link util.visitor.Visited} subclasses that use this visitor. * @public - * @param adt {commands.util.proxy.Asynchronous} adt used for asynchronous operations + * @param adt {util.proxy.Asynchronous} adt used for asynchronous operations * @param resolve {Function} asynchronous promise's resolve * @param reject {Function} asynchronous promise's reject * @return {Promise} @@ -29,8 +29,8 @@ class Asynchronous extends Visitor { /** * Default strategy to perform an asynchronous operation * @public - * @param {commands.util.visitor.Visited} [ctx] - context reference - * @param {commands.visitors.async.Asynchronous} adt - reference to adt using this interface on their elements + * @param {util.visitor.Visited} [ctx] - context reference + * @param {visitors.async.Asynchronous} adt - reference to adt using this interface on their elements * @return {Promise} **/ execute(ctx, adt) { diff --git a/lib/visitors/commander.es6 b/lib/visitors/commander.es6 new file mode 100644 index 0000000..7f0bbd5 --- /dev/null +++ b/lib/visitors/commander.es6 @@ -0,0 +1,95 @@ +/** +* @module visitors +* @author Patricio Ferreira <3dimentionar@gmail.com> +**/ +import _ from 'underscore'; +import extend from 'extend'; +import yargs from 'yargs'; +import Factory from 'util/factory/factory'; +import Collection from 'util/adt/collection'; +import Visitor from 'util/visitor/visitor'; + +/** +* Class Commander +* @extends {util.visitor.Visitor} +**/ +class Commander extends Visitor { + + /** + * Constructor + * @public + * @override + * @param {Any} [...args] - constructor arguments + * @return {visitors.Commander} + **/ + constructor(...args) { + super(...args); + return extend(true, this, { commands: Collection.new(this.constructor.commands) }).build(); + } + + /** + * Parses Configuration options + * @public + * @param {util.visitor.Visited} ctx - context reference + * @return {visitors.Commander} + **/ + build(ctx) { + let args = this.reduce(_.bind(this.command, this, ctx), this.usage()).help().argv; + return this; + } + + /** + * Adds Usage message + * @public + * @param {} + * @return {yargs} + **/ + usage(message = '$0 [args]') { + return yargs.usage(message); + } + + /** + * Reducer appends a command into the memoized yargs reference + * @public + * @param {util.visitor.Visited} ctx - context reference + * @param {yargs} memo - memoized yargs reference + * @param {Object} name - command name and abbreviation + * @param {String} [description = ''] - command description + * @return {yargs} + **/ + command(ctx, memo, name, description = '') { + return memo.command(this.name(name), description, defaults, ctx[name]); + } + + /** + * Build and returns yargs command with a given name options + * @public + * @param {Object} name - command name and abbreviation. i.e: { long: 'name', abbr: 'n' } + * @return {String} + **/ + name(name) { + return `${name.long}` + (_.defined(name.abbr) ? ` [${name.abbr}]` : ''); + } + + /** + * Visitor Name + * @public + * @type {String} + **/ + get name() { + return 'CommanderVisitor'; + } + + /** + * Move to Configuration - Available Artifacts + * @static + * @type {Array} + **/ + static artifacts = [ + 'visitors/configuration/remote', + 'visitors/configuration/local' + ]; + +} + +export default Commander; diff --git a/src/commands/visitors/commander.es6 b/lib/visitors/configuration.es6 similarity index 58% rename from src/commands/visitors/commander.es6 rename to lib/visitors/configuration.es6 index 49713c3..84e31b8 100644 --- a/src/commands/visitors/commander.es6 +++ b/lib/visitors/configuration.es6 @@ -1,25 +1,25 @@ /** -* @module commands.visitors +* @module visitors * @author Patricio Ferreira <3dimentionar@gmail.com> **/ import _ from 'underscore'; import extend from 'extend'; -import yargs from 'yargs'; -import Visitor from 'commands/util/visitor/visitor'; -import QueueAsync from 'commands/util/adt/queue-async'; +import Factory from 'util/factory/factory'; +import Visitor from 'util/visitor/visitor'; +import QueueAsync from 'util/adt/queue-async'; /** -* Class Commander -* @extends {commands.util.visitor.Visitor} +* Class Configuration +* @extends {util.visitor.Visitor} **/ -class Commander extends Visitor { +class Configuration extends Visitor { /** * Constructor * @public * @override * @param {Any} [...args] - constructor arguments - * @return {commands.visitors.Commander} + * @return {visitors.Configuration} **/ constructor(...args) { super(...args); @@ -27,12 +27,12 @@ class Commander extends Visitor { } /** - * Parses Configuration options + * Load Configuration * @public - * @param {commands.util.visitor.Visited} ctx - context reference - * @return {commands.visitors.Commander} + * @param {util.visitor.Visited} ctx - context reference + * @return {visitors.Commander} **/ - parse(ctx) { + load(ctx) { // TODO: [js,json,uri] QueueAsync for executing for configuration return this; } @@ -43,20 +43,11 @@ class Commander extends Visitor { * @type {String} **/ get name() { - return 'CommanderVisitor'; + return 'ConfigurationVisitor'; } /** - * Available Artifacts - * @static - * @type {Array} - **/ - static artifacts = [ - 'configuration' - ]; // TODO - - /** - * Commander Defaults + * Configuration Defaults * @static * @type {Object} **/ @@ -70,7 +61,7 @@ class Commander extends Visitor { }; /** - * Commander options + * Configuration options * @static * @type {Array} **/ @@ -85,4 +76,4 @@ class Commander extends Visitor { } -export default Commander; +export default Configuration; diff --git a/src/commands/visitors/configuration/remote.es6 b/lib/visitors/configuration/local.es6 similarity index 100% rename from src/commands/visitors/configuration/remote.es6 rename to lib/visitors/configuration/local.es6 diff --git a/src/commands/visualize/visualize.es6 b/lib/visitors/configuration/remote.es6 similarity index 100% rename from src/commands/visualize/visualize.es6 rename to lib/visitors/configuration/remote.es6 diff --git a/src/commands/visitors/formatter/json.es6 b/lib/visitors/formatter/json.es6 similarity index 81% rename from src/commands/visitors/formatter/json.es6 rename to lib/visitors/formatter/json.es6 index 995a349..8bed669 100644 --- a/src/commands/visitors/formatter/json.es6 +++ b/lib/visitors/formatter/json.es6 @@ -1,14 +1,14 @@ /** -* @module commands.visitors.formatter +* @module visitors.formatter * @author Patricio Ferreira <3dimentionar@gmail.com> **/ import _ from 'underscore'; -import Visitor from 'commands/util/visitor/visitor'; -import InterfaceException from 'commands/util/exception/proxy/interface'; +import Visitor from 'util/visitor/visitor'; +import InterfaceException from 'util/exception/proxy/interface'; /** * Class JSON -* @extends {commands.util.visitor.Visitor} +* @extends {util.visitor.Visitor} **/ class Json extends Visitor { @@ -44,7 +44,7 @@ class Json extends Visitor { * Returns a json representation of the instance of this class * This method uses recursion * @public - * @param {commands.visitors.formatter.Json} [ctx] - context reference + * @param {visitors.formatter.Json} [ctx] - context reference * @return {Object} **/ toJSON(ctx) { diff --git a/test/commands/visitors/commander.spec.es6 b/lib/visualize/visualize.es6 similarity index 100% rename from test/commands/visitors/commander.spec.es6 rename to lib/visualize/visualize.es6 diff --git a/package.json b/package.json index 099356d..75b5e62 100644 --- a/package.json +++ b/package.json @@ -7,12 +7,11 @@ "sqbox": "bin/sqbox" }, "scripts": { - "test": "_mocha --opts mocha.opts --watch test/commands", + "test": "_mocha --opts mocha.opts --watch test/lib", "it": "_mocha --opts mocha.opts test/integration", - "coverage": "cross-env NODE_ENV=test nyc --reporter=lcov --reporter=html --reporter=text mocha --opts mocha.opts test/commands", + "coverage": "cross-env NODE_ENV=test nyc --reporter=lcov --reporter=html --reporter=text mocha --opts mocha.opts test/lib", "coveralls": "cat ./coverage/lcov.info | node node_modules/.bin/coveralls", - "docs": "esdoc", - "build": "node release" + "docs": "esdoc" }, "nyc": { "require": [ @@ -38,11 +37,6 @@ "devDependencies": { "babel-cli": "6.23.0", "babel-plugin-istanbul": "4.0.0", - "babel-plugin-module-resolver": "2.5.0", - "babel-polyfill": "6.23.0", - "babel-preset-es2015": "6.22.0", - "babel-preset-stage-2": "6.22.0", - "babel-register": "6.23.0", "chai": "3.5.0", "coveralls": "2.11.16", "cross-env": "3.1.4", @@ -54,7 +48,11 @@ "sinon": "1.17.7" }, "dependencies": { - "async": "^2.1.5", + "babel-plugin-module-resolver": "2.5.0", + "babel-polyfill": "6.23.0", + "babel-preset-es2015": "6.22.0", + "babel-preset-stage-2": "6.22.0", + "babel-register": "6.23.0", "chalk": "1.1.3", "esprima": "3.1.3", "extend": "3.0.0", diff --git a/release.js b/release.js deleted file mode 100644 index 2bfe03a..0000000 --- a/release.js +++ /dev/null @@ -1,6 +0,0 @@ -/** -* Release Squarebox -* @author Patricio Ferreira <3dimentionar@gmail.com> -**/ -require('babel-register')(); -require('./src/build/build'); diff --git a/src/build/build.es6 b/src/build/build.es6 deleted file mode 100644 index 3e18e44..0000000 --- a/src/build/build.es6 +++ /dev/null @@ -1,38 +0,0 @@ -/** -* @module build -* @author Patricio Ferreira <3dimentionar@gmail.com> -**/ -import Package from '../../../package.json'; -import Command from 'commands/command'; -import _ from 'underscore'; -import extend from 'extend'; -import yargs from 'yargs'; - -/** -* Class Build -* @extends {command.Command} -**/ -class Build extends Command { - - /** - * Build Run - * @public - * @override - * @return {build.Build} - **/ - run() { - return this; - } - - /** - * Static Run - * @static - * @return {build.Build} - **/ - static run() { - return new this(); - } - -} - -export default Build.run(); diff --git a/test/global.js b/test/global.js index 77da15f..6511fbb 100644 --- a/test/global.js +++ b/test/global.js @@ -7,4 +7,4 @@ global.path = require('path'); global._ = require('underscore'); global.sinon = require('sinon'); global.assert = require('chai').assert; -global.basepath = path.join(path.resolve(__dirname, '..'), 'src'); +global.basepath = path.join(path.resolve(__dirname, '..'), 'lib'); diff --git a/test/commands/bin/sqbox.spec.es6 b/test/lib/bin/sqbox.spec.es6 similarity index 80% rename from test/commands/bin/sqbox.spec.es6 rename to test/lib/bin/sqbox.spec.es6 index 0c5c481..59eecdc 100644 --- a/test/commands/bin/sqbox.spec.es6 +++ b/test/lib/bin/sqbox.spec.es6 @@ -1,8 +1,8 @@ /** -* @module commands.bin +* @module bin * @author Patricio Ferreira <3dimentionar@gmail.com> **/ -describe('commands.bin.SquareBox', function() { +describe('bin.SquareBox', function() { before(() => { this.sandbox = sinon.sandbox.create(); @@ -29,7 +29,7 @@ describe('commands.bin.SquareBox', function() { describe('constructor()', () => { it('Should get an instance', () => { - this.sqbox = require('commands/bin/sqbox'); + this.sqbox = require('bin/sqbox'); }); }); diff --git a/test/commands/command.spec.es6 b/test/lib/command.spec.es6 similarity index 85% rename from test/commands/command.spec.es6 rename to test/lib/command.spec.es6 index 5135c3c..15d4710 100644 --- a/test/commands/command.spec.es6 +++ b/test/lib/command.spec.es6 @@ -1,10 +1,10 @@ /** -* @module commands +* @module * @author Patricio Ferreira <3dimentionar@gmail.com> **/ -import Command from 'commands/command'; +import Command from 'command'; -describe('commands.Command', function() { +describe('Command', function() { before(() => { this.sandbox = sinon.sandbox.create(); diff --git a/test/commands/util/adt/collection.spec.es6 b/test/lib/util/adt/collection.spec.es6 similarity index 98% rename from test/commands/util/adt/collection.spec.es6 rename to test/lib/util/adt/collection.spec.es6 index 70b33c2..f2e2d3e 100644 --- a/test/commands/util/adt/collection.spec.es6 +++ b/test/lib/util/adt/collection.spec.es6 @@ -1,12 +1,12 @@ /** -* @module commands.util.adt +* @module util.adt * @author Patricio Ferreira <3dimentionar@gmail.com> **/ -import Collection from 'commands/util/adt/collection'; -import Command from 'commands/command'; -import Iterator from 'commands/util/adt/iterator'; +import Collection from 'util/adt/collection'; +import Command from 'command'; +import Iterator from 'util/adt/iterator'; -describe('commands.util.adt.Collection', function() { +describe('util.adt.Collection', function() { before(() => { this.sandbox = sinon.sandbox.create(); diff --git a/test/commands/util/adt/iterator.spec.es6 b/test/lib/util/adt/iterator.spec.es6 similarity index 95% rename from test/commands/util/adt/iterator.spec.es6 rename to test/lib/util/adt/iterator.spec.es6 index b23b6e3..42a1a91 100644 --- a/test/commands/util/adt/iterator.spec.es6 +++ b/test/lib/util/adt/iterator.spec.es6 @@ -1,10 +1,10 @@ /** -* @module commands.util.adt +* @module util.adt * @author Patricio Ferreira <3dimentionar@gmail.com> **/ -import Iterator from 'commands/util/adt/iterator'; +import Iterator from 'util/adt/iterator'; -describe('commands.util.adt.Iterator', function() { +describe('util.adt.Iterator', function() { before(() => { this.sandbox = sinon.sandbox.create(); diff --git a/test/commands/util/adt/queue-async.spec.es6 b/test/lib/util/adt/queue-async.spec.es6 similarity index 80% rename from test/commands/util/adt/queue-async.spec.es6 rename to test/lib/util/adt/queue-async.spec.es6 index e39ca0a..130fa2b 100644 --- a/test/commands/util/adt/queue-async.spec.es6 +++ b/test/lib/util/adt/queue-async.spec.es6 @@ -1,13 +1,13 @@ /** -* @module commands.util.adt +* @module util.adt * @author Patricio Ferreira <3dimentionar@gmail.com> **/ -import QueueAsync from 'commands/util/adt/queue-async'; -import Command from 'commands/command'; -import Asynchronous from 'commands/visitors/async/async'; -import InterfaceException from 'commands/util/exception/proxy/interface'; +import QueueAsync from 'util/adt/queue-async'; +import Command from 'command'; +import Asynchronous from 'visitors/async/async'; +import InterfaceException from 'util/exception/proxy/interface'; -describe('commands.util.adt.QueueAsync', function() { +describe('util.adt.QueueAsync', function() { before(() => { this.sandbox = sinon.sandbox.create(); @@ -80,8 +80,8 @@ describe('commands.util.adt.QueueAsync', function() { }).catch((err) => console.log(err)); }); - it('Should Error: Element doesn\'t implement interface commands.util.visitor.Visited', () => { - const message = InterfaceException.type.interface({ name: 'commands.util.visitor.Visited' }); + it('Should Error: Element doesn\'t implement interface util.visitor.Visited', () => { + const message = InterfaceException.type.interface({ name: 'util.visitor.Visited' }); assert.throws(() => QueueAsync.new([{ simple: true }], { capacity: 1 }), message); }); diff --git a/test/commands/util/adt/queue.spec.es6 b/test/lib/util/adt/queue.spec.es6 similarity index 94% rename from test/commands/util/adt/queue.spec.es6 rename to test/lib/util/adt/queue.spec.es6 index f18d0f3..14f893d 100644 --- a/test/commands/util/adt/queue.spec.es6 +++ b/test/lib/util/adt/queue.spec.es6 @@ -1,12 +1,12 @@ /** -* @module commands.util.adt +* @module util.adt * @author Patricio Ferreira <3dimentionar@gmail.com> **/ -import Queue from 'commands/util/adt/queue'; -import Command from 'commands/command'; -import QueueException from 'commands/util/exception/adt/queue'; +import Queue from 'util/adt/queue'; +import Command from 'command'; +import QueueException from 'util/exception/adt/queue'; -describe('commands.util.adt.Queue', function() { +describe('util.adt.Queue', function() { before(() => { this.sandbox = sinon.sandbox.create(); diff --git a/test/commands/util/adt/stack-async.spec.es6 b/test/lib/util/adt/stack-async.spec.es6 similarity index 79% rename from test/commands/util/adt/stack-async.spec.es6 rename to test/lib/util/adt/stack-async.spec.es6 index ef137ff..a5df7ae 100644 --- a/test/commands/util/adt/stack-async.spec.es6 +++ b/test/lib/util/adt/stack-async.spec.es6 @@ -1,13 +1,13 @@ /** -* @module commands.util.adt +* @module util.adt * @author Patricio Ferreira <3dimentionar@gmail.com> **/ -import StackAsync from 'commands/util/adt/stack-async'; -import Command from 'commands/command'; -import Asynchronous from 'commands/visitors/async/async'; -import InterfaceException from 'commands/util/exception/proxy/interface'; +import StackAsync from 'util/adt/stack-async'; +import Command from 'command'; +import Asynchronous from 'visitors/async/async'; +import InterfaceException from 'util/exception/proxy/interface'; -describe('commands.util.adt.StackAsync', function() { +describe('util.adt.StackAsync', function() { before(() => { this.sandbox = sinon.sandbox.create(); @@ -80,8 +80,8 @@ describe('commands.util.adt.StackAsync', function() { }).catch((err) => console.log(err)); }); - it('Should Error: Element doesn\'t implement interface commands.util.visitor.Visited', () => { - const message = InterfaceException.type.interface({ name: 'commands.util.visitor.Visited' }); + it('Should Error: Element doesn\'t implement interface util.visitor.Visited', () => { + const message = InterfaceException.type.interface({ name: 'util.visitor.Visited' }); assert.throws(() => StackAsync.new([{ simple: true }]), message); }); diff --git a/test/commands/util/adt/stack.spec.es6 b/test/lib/util/adt/stack.spec.es6 similarity index 94% rename from test/commands/util/adt/stack.spec.es6 rename to test/lib/util/adt/stack.spec.es6 index b07991d..cb80b0b 100644 --- a/test/commands/util/adt/stack.spec.es6 +++ b/test/lib/util/adt/stack.spec.es6 @@ -1,11 +1,11 @@ /** -* @module commands.util.adt +* @module util.adt * @author Patricio Ferreira <3dimentionar@gmail.com> **/ -import Stack from 'commands/util/adt/stack'; -import Command from 'commands/command'; +import Stack from 'util/adt/stack'; +import Command from 'command'; -describe('commands.util.adt.Stack', function() { +describe('util.adt.Stack', function() { before(() => { this.sandbox = sinon.sandbox.create(); diff --git a/test/commands/util/factory/factory.spec.es6 b/test/lib/util/factory/factory.spec.es6 similarity index 94% rename from test/commands/util/factory/factory.spec.es6 rename to test/lib/util/factory/factory.spec.es6 index 67f30be..7695568 100644 --- a/test/commands/util/factory/factory.spec.es6 +++ b/test/lib/util/factory/factory.spec.es6 @@ -1,13 +1,13 @@ /** -* @module commands.util.factory +* @module util.factory * @author Patricio Ferreira <3dimentionar@gmail.com> **/ -import factory from 'commands/util/factory/factory'; -import logger from 'commands/util/logger/logger'; -import Command from 'commands/command'; -import Collection from 'commands/util/adt/collection'; +import factory from 'util/factory/factory'; +import logger from 'util/logger/logger'; +import Command from 'command'; +import Collection from 'util/adt/collection'; -describe('commands.util.factory.Factory', function() { +describe('util.factory.Factory', function() { before(() => { factory.reset(); @@ -50,15 +50,15 @@ describe('commands.util.factory.Factory', function() { describe('basePath()', () => { it('Should set a basePath', () => { - const input = './src/commands'; + const input = './lib'; assert.typeOf(factory.basePath(input), 'object'); assert.equal(input, factory.path); }); it('Should be same basePath when importing multiple times (singleton check)', () => { - const singleton = require('commands/util/factory/factory').default; + const singleton = require('util/factory/factory').default; assert.deepEqual(singleton, factory); - singleton.basePath('./src/commands/util/adt'); + singleton.basePath('./lib/util/adt'); assert.equal(singleton.path, factory.path); }); diff --git a/test/commands/util/logger/logger.spec.es6 b/test/lib/util/logger/logger.spec.es6 similarity index 97% rename from test/commands/util/logger/logger.spec.es6 rename to test/lib/util/logger/logger.spec.es6 index caffeae..6c71bcf 100644 --- a/test/commands/util/logger/logger.spec.es6 +++ b/test/lib/util/logger/logger.spec.es6 @@ -1,10 +1,10 @@ /** -* @module commands.util.logger +* @module util.logger * @author Patricio Ferreira <3dimentionar@gmail.com> **/ -import logger, { Logger } from 'commands/util/logger/logger'; +import logger, { Logger } from 'util/logger/logger'; -describe('commands.util.logger.Logger', function() { +describe('util.logger.Logger', function() { before(() => { this.sandbox = sinon.sandbox.create(); diff --git a/test/commands/util/mixins.spec.es6 b/test/lib/util/mixins.spec.es6 similarity index 96% rename from test/commands/util/mixins.spec.es6 rename to test/lib/util/mixins.spec.es6 index d57d23f..557eeef 100644 --- a/test/commands/util/mixins.spec.es6 +++ b/test/lib/util/mixins.spec.es6 @@ -1,10 +1,10 @@ /** -* @module commands.util +* @module util * @author Patricio Ferreira <3dimentionar@gmail.com> **/ -import _ from 'commands/util/mixins'; +import _ from 'util/mixins'; -describe('commands.util.mixins', function() { +describe('util.mixins', function() { before(() => { this.sandbox = sinon.sandbox.create(); diff --git a/test/commands/util/visitor/visited.spec.es6 b/test/lib/util/visitor/visited.spec.es6 similarity index 93% rename from test/commands/util/visitor/visited.spec.es6 rename to test/lib/util/visitor/visited.spec.es6 index 5d24432..3ce4c1f 100644 --- a/test/commands/util/visitor/visited.spec.es6 +++ b/test/lib/util/visitor/visited.spec.es6 @@ -1,12 +1,12 @@ /** -* @module commands.util.visitor +* @module util.visitor * @author Patricio Ferreira <3dimentionar@gmail.com> **/ -import Visited from 'commands/util/visitor/visited'; -import Visitor from 'commands/util/visitor/visitor'; -import InterfaceException from 'commands/util/exception/proxy/interface'; +import Visited from 'util/visitor/visited'; +import Visitor from 'util/visitor/visitor'; +import InterfaceException from 'util/exception/proxy/interface'; -describe('commands.util.visitor.Visited', function() { +describe('util.visitor.Visited', function() { before(() => { this.sandbox = sinon.sandbox.create(); diff --git a/test/commands/util/visitor/visitor.spec.es6 b/test/lib/util/visitor/visitor.spec.es6 similarity index 82% rename from test/commands/util/visitor/visitor.spec.es6 rename to test/lib/util/visitor/visitor.spec.es6 index 5006841..141b781 100644 --- a/test/commands/util/visitor/visitor.spec.es6 +++ b/test/lib/util/visitor/visitor.spec.es6 @@ -1,12 +1,12 @@ /** -* @module commands.util.visitor +* @module util.visitor * @author Patricio Ferreira <3dimentionar@gmail.com> **/ -import Visitor from 'commands/util/visitor/visitor'; -import Visited from 'commands/util/visitor/visited'; -import InterfaceException from 'commands/util/exception/proxy/interface'; +import Visitor from 'util/visitor/visitor'; +import Visited from 'util/visitor/visited'; +import InterfaceException from 'util/exception/proxy/interface'; -describe('commands.util.visitor.Visitor', function() { +describe('util.visitor.Visitor', function() { before(() => { this.sandbox = sinon.sandbox.create(); @@ -52,7 +52,7 @@ describe('commands.util.visitor.Visitor', function() { it('Should throw InterfaceException: visited doesn\'t implement his interface', () => { const exp = Visitor.new(); assert.throws(() => exp.validate({}), - InterfaceException.type.interface({ name: 'commands.util.visitor.Visited' })); + InterfaceException.type.interface({ name: 'util.visitor.Visited' })); }); }); diff --git a/test/commands/visitors/async/async.spec.es6 b/test/lib/visitors/async/async.spec.es6 similarity index 85% rename from test/commands/visitors/async/async.spec.es6 rename to test/lib/visitors/async/async.spec.es6 index b1ca84c..518d3ba 100644 --- a/test/commands/visitors/async/async.spec.es6 +++ b/test/lib/visitors/async/async.spec.es6 @@ -1,13 +1,13 @@ /** -* @module commands.visitors.async +* @module visitors.async * @author Patricio Ferreira <3dimentionar@gmail.com> **/ import _ from 'underscore'; -import Asynchronous from 'commands/visitors/async/async'; -import Visited from 'commands/util/visitor/visited'; -import InterfaceException from 'commands/util/exception/proxy/interface'; +import Asynchronous from 'visitors/async/async'; +import Visited from 'util/visitor/visited'; +import InterfaceException from 'util/exception/proxy/interface'; -describe('commands.visitors.async.Asynchronous', function() { +describe('visitors.async.Asynchronous', function() { before(() => { this.sandbox = sinon.sandbox.create(); diff --git a/test/lib/visitors/commander.spec.es6 b/test/lib/visitors/commander.spec.es6 new file mode 100644 index 0000000..e69de29 diff --git a/test/commands/visitors/formatter/json.spec.es6 b/test/lib/visitors/formatter/json.spec.es6 similarity index 80% rename from test/commands/visitors/formatter/json.spec.es6 rename to test/lib/visitors/formatter/json.spec.es6 index e0d1842..c45594e 100644 --- a/test/commands/visitors/formatter/json.spec.es6 +++ b/test/lib/visitors/formatter/json.spec.es6 @@ -1,14 +1,14 @@ /** -* @module commands.visitors.formatter +* @module visitors.formatter * @author Patricio Ferreira <3dimentionar@gmail.com> **/ import _ from 'underscore'; -import Json from 'commands/visitors/formatter/json'; -import Visited from 'commands/util/visitor/visited'; -import Command from 'commands/command'; -import InterfaceException from 'commands/util/exception/proxy/interface'; +import Json from 'visitors/formatter/json'; +import Visited from 'util/visitor/visited'; +import Command from 'command'; +import InterfaceException from 'util/exception/proxy/interface'; -describe('commands.visitors.formatter.Json', function() { +describe('visitors.formatter.Json', function() { before(() => { this.sandbox = sinon.sandbox.create(); @@ -66,7 +66,7 @@ describe('commands.visitors.formatter.Json', function() { const input = { property: 'target' }; const exp = Json.new(); assert.throws(() => exp.visit(input), InterfaceException.type - .interface({ name: 'commands.util.visitor.Visited' })); + .interface({ name: 'util.visitor.Visited' })); }); }); From 232e5bacf746f6943a85506788b7fb31d9ed3706 Mon Sep 17 00:00:00 2001 From: kuakman <3dimentionar@gmail.com> Date: Tue, 14 Mar 2017 11:33:06 -0700 Subject: [PATCH 22/39] cli: checkpoint - progress on visitors.Commander. Yargs commands were externalize on a json file. Added dummy code to bundle, help, clean and graph. --- lib/bin/commands.json | 38 +++++++ lib/bin/sqbox.es6 | 21 ++-- lib/bundle/bundle.es6 | 29 ++++++ lib/clean/clean.es6 | 29 ++++++ lib/command.es6 | 7 +- lib/help/help.es6 | 29 ++++++ lib/visitors/async/async.es6 | 11 ++ lib/visitors/commander.es6 | 150 ++++++++++++++++++++++----- lib/visitors/configuration.es6 | 6 +- lib/visitors/formatter/json.es6 | 11 ++ lib/visualize/graph.es6 | 29 ++++++ lib/visualize/visualize.es6 | 0 mocha.opts | 2 +- test/lib/bin/sqbox.spec.es6 | 39 +++++-- test/lib/visitors/commander.spec.es6 | 1 + 15 files changed, 352 insertions(+), 50 deletions(-) create mode 100644 lib/bin/commands.json create mode 100644 lib/visualize/graph.es6 delete mode 100644 lib/visualize/visualize.es6 diff --git a/lib/bin/commands.json b/lib/bin/commands.json new file mode 100644 index 0000000..23aba0d --- /dev/null +++ b/lib/bin/commands.json @@ -0,0 +1,38 @@ +[{ + "name": "help", + "aliases": ["h"], + "description": "Help", + "path": "help/help", + "options": { + "clean": { "alias": "c" }, + "bundle": { "alias": "b" }, + "graph": { "alias": "g" } + } +}, { + "name": "clean", + "aliases": ["c"], + "description": "Clean output directory", + "path": "clean/clean", + "options": { + "config": { "alias": "c", "default": ".sqboxrc" }, + "url": { "alias": "u" } + } +}, { + "name": "bundle", + "aliases": ["b"], + "description": "Module Bundler", + "path": "bundle/bundle", + "options": { + "config": { "alias": "c", "default": ".sqboxrc" }, + "url": { "alias": "u" } + } +}, { + "name": "graph", + "aliases": ["g"], + "description": "Generates a graphical representation of your bundles", + "path": "visualize/graph", + "options": { + "config": { "alias": "c", "default": ".sqboxrc" }, + "url": { "alias": "u" } + } +}] diff --git a/lib/bin/sqbox.es6 b/lib/bin/sqbox.es6 index f24997f..ea5239b 100644 --- a/lib/bin/sqbox.es6 +++ b/lib/bin/sqbox.es6 @@ -4,9 +4,10 @@ **/ import 'util/mixins'; import extend from 'extend'; - +import Collection from 'util/adt/collection'; import Factory from 'util/factory/factory'; import Command from 'command'; +import CommandsList from './commands.json'; let enforcer = Symbol('SquareBox'); @@ -43,10 +44,11 @@ class SquareBox extends Command { * Before Run * @public * @override - * @return {bin.SquareBox} + * @return {Object} **/ before() { - return super.before().parse(); + super.before().build(); + return this; } /** @@ -88,14 +90,9 @@ class SquareBox extends Command { /** * Available Commands * @static - * @type {Array} + * @type {util.adt.Collection} **/ - static commands = [ - { help: 'help/help' }, - { clean: 'clean/clean' }, - { bundle: 'bundle/bundle' }, - { visualize: 'visualize/visualize' } - ]; + static commands = Collection.new(CommandsList); /** * SquareBox visitors @@ -124,8 +121,8 @@ class SquareBox extends Command { * @param {String} [cwd = process.cwd()] - base path * @return {bin.SquareBox} **/ - static run(cwd) { - return this.new({ cwd }).read(); + static run(cwd = process.cwd()) { + return this.new({ cwd }).run(); } } diff --git a/lib/bundle/bundle.es6 b/lib/bundle/bundle.es6 index e69de29..5f228bc 100644 --- a/lib/bundle/bundle.es6 +++ b/lib/bundle/bundle.es6 @@ -0,0 +1,29 @@ +/** +* @module bundle +* @author Patricio Ferreira <3dimentionar@gmail.com> +**/ +import _ from 'underscore'; +import extend from 'extend'; +import Command from 'command'; + +/** +* Class Bundle +* @extends {Command} +**/ +class Bundle extends Command { + + /** + * Run + * @public + * @override + * @return {bundle.Bundle} + **/ + run() { + // TODO + console.log('Bundle.run()...'); + return super.run(); + } + +} + +export default Bundle; diff --git a/lib/clean/clean.es6 b/lib/clean/clean.es6 index e69de29..0f9eaea 100644 --- a/lib/clean/clean.es6 +++ b/lib/clean/clean.es6 @@ -0,0 +1,29 @@ +/** +* @module clean +* @author Patricio Ferreira <3dimentionar@gmail.com> +**/ +import _ from 'underscore'; +import extend from 'extend'; +import Command from 'command'; + +/** +* Class Clean +* @extends {Command} +**/ +class Clean extends Command { + + /** + * Run + * @public + * @override + * @return {clean.Clean} + **/ + run() { + // TODO + console.log('Clean.run()...'); + return super.run(); + } + +} + +export default Clean; diff --git a/lib/command.es6 b/lib/command.es6 index 6c164b2..ac44a0d 100644 --- a/lib/command.es6 +++ b/lib/command.es6 @@ -212,7 +212,12 @@ class Command extends Visited { static options = [ 'env', 'dirname', - 'cwd' + 'cwd', + 'name', + 'aliases', + 'params', + 'description', + 'options' ]; /** diff --git a/lib/help/help.es6 b/lib/help/help.es6 index e69de29..51b7639 100644 --- a/lib/help/help.es6 +++ b/lib/help/help.es6 @@ -0,0 +1,29 @@ +/** +* @module help +* @author Patricio Ferreira <3dimentionar@gmail.com> +**/ +import _ from 'underscore'; +import extend from 'extend'; +import Command from 'command'; + +/** +* Class Clean +* @extends {Command} +**/ +class Clean extends Command { + + /** + * Run + * @public + * @override + * @return {help.Clean} + **/ + run() { + // TODO + console.log('Help.run()...'); + return super.run(); + } + +} + +export default Clean; diff --git a/lib/visitors/async/async.es6 b/lib/visitors/async/async.es6 index 4b43205..27b5e19 100644 --- a/lib/visitors/async/async.es6 +++ b/lib/visitors/async/async.es6 @@ -12,6 +12,17 @@ import Visitor from 'util/visitor/visitor'; **/ class Asynchronous extends Visitor { + /** + * Constructor + * @public + * @override + * @param {Any} [...args] - constructor arguments + * @return {visitors.async.Asynchronous} + **/ + constructor(...args) { + return super(); + } + /** * Default Next Strategy to always resolve the promise. * Note: This method was designed (and it's most likely) to be overriden by diff --git a/lib/visitors/commander.es6 b/lib/visitors/commander.es6 index 7f0bbd5..60eeca3 100644 --- a/lib/visitors/commander.es6 +++ b/lib/visitors/commander.es6 @@ -5,6 +5,7 @@ import _ from 'underscore'; import extend from 'extend'; import yargs from 'yargs'; +import chalk from 'chalk'; import Factory from 'util/factory/factory'; import Collection from 'util/adt/collection'; import Visitor from 'util/visitor/visitor'; @@ -16,36 +17,125 @@ import Visitor from 'util/visitor/visitor'; class Commander extends Visitor { /** - * Constructor + * Adds Program Version * @public - * @override - * @param {Any} [...args] - constructor arguments - * @return {visitors.Commander} + * @param {String} version - program version + * @return {yargs} **/ - constructor(...args) { - super(...args); - return extend(true, this, { commands: Collection.new(this.constructor.commands) }).build(); + _version() { + // FIXME: Get it from package.json + return yargs.version('1.0.0'); } /** - * Parses Configuration options + * Adds Usage message * @public - * @param {util.visitor.Visited} ctx - context reference - * @return {visitors.Commander} + * @param {String} [message = 'sqbox [args]'] - usage message to display + * @return {yargs} **/ - build(ctx) { - let args = this.reduce(_.bind(this.command, this, ctx), this.usage()).help().argv; + _usage(message = 'sqbox [args]') { + yargs.usage(chalk.white(message)); return this; } /** - * Adds Usage message + * Default Strategy that returns Command name using yargs convention format + * @private + * @param {Command} command - command instance + * @return {String} + **/ + _command(command) { + const { name, abbr } = command; + return chalk.green(`${name}` + (_.defined(abbr) ? `, -${abbr}` : '')); + } + + /** + * Default Strategy that returns Command's aliases * @public - * @param {} + * @param {Command} command - command instance + * @return {Array} + **/ + _aliases(command) { + return command.aliases; + } + + /** + * Default Strategy that returns Command Description + * @private + * @param {Command} command - command instance + * @return {String} + **/ + _desc(command) { + return chalk.yellow(command.description); + } + + /** + * Default Strategy that returns Command Options defaults + * @private + * @param {Command} command - command instance + * @return {Object} + **/ + _builder(command) { + return command.options; + } + + /** + * Default Strategy that returns Command Handler + * @private + * @param {util.visitor.Visited} ctx - context reference + * @return {Function} + **/ + _handler(command, ctx) { + const instance = Factory.register(command.path).get(command.path); + return _.bind(instance.run, instance); + } + + /** + * Epilogue Information to show at the end + * @private + * @return {String} + **/ + _epilogue() { + return chalk.cyan(`For more information about the configuration, please visit http://squarebox.nahuel.io/`); + } + + /** + * Parses Configuration options + * @public + * @param {util.visitor.Visited} ctx - context reference + * @param {Function} parse - * @return {yargs} **/ - usage(message = '$0 [args]') { - return yargs.usage(message); + build(ctx, parse) { + const { commands } = ctx.constructor; + yargs.reset(); + return commands.reduce(_.bind(this.command, this, ctx), this._usage()._version()) + .epilogue(this._epilogue()) + .wrap(120) + .parse(this.programArgv(), this.onParse); + } + + /** + * Default Strategy to retrieve program arguments + * @private + * @param {util.visitor.Visited} ctx - context reference + * @param {Any} [args = process.argv] - optional arguments + * @return {Array} + **/ + programArgv(ctx, args = process.argv) { + return args; + } + + /** + * Default Parse Handler + * @public + * @param {Error} [err] - error reference while parsing + * @param {Object} argv - arguments parsed + * @param {Object} output - output reference + * @return {visitors.Commander} + **/ + onParse(err, argv, output) { + return this; } /** @@ -53,22 +143,25 @@ class Commander extends Visitor { * @public * @param {util.visitor.Visited} ctx - context reference * @param {yargs} memo - memoized yargs reference - * @param {Object} name - command name and abbreviation + * @param {Command} command - command instance * @param {String} [description = ''] - command description * @return {yargs} **/ - command(ctx, memo, name, description = '') { - return memo.command(this.name(name), description, defaults, ctx[name]); + command(ctx, memo, command, description = '') { + return memo.command(_.reduce(Commander.builder(), _.bind(this.newCommand, this, command), {}, this)); } /** - * Build and returns yargs command with a given name options + * Build a new command object and * @public - * @param {Object} name - command name and abbreviation. i.e: { long: 'name', abbr: 'n' } - * @return {String} + * @param {Command} command - command instance + * @param {Object} memo - memoized object + * @param {String} option - Yargs command option + * @return {Object} **/ - name(name) { - return `${name.long}` + (_.defined(name.abbr) ? ` [${name.abbr}]` : ''); + newCommand(command, memo, option) { + memo[option] = this[`_${option}`](command); + return memo; } /** @@ -80,6 +173,15 @@ class Commander extends Visitor { return 'CommanderVisitor'; } + /** + * Yargs Command Builder option names + * @static + * @return {Array} + **/ + static builder() { + return ['command', 'aliases', 'desc', 'builder', 'handler']; + } + /** * Move to Configuration - Available Artifacts * @static diff --git a/lib/visitors/configuration.es6 b/lib/visitors/configuration.es6 index 84e31b8..935774f 100644 --- a/lib/visitors/configuration.es6 +++ b/lib/visitors/configuration.es6 @@ -18,11 +18,11 @@ class Configuration extends Visitor { * Constructor * @public * @override - * @param {Any} [...args] - constructor arguments + * @param {Command} commnand - command reference * @return {visitors.Configuration} **/ - constructor(...args) { - super(...args); + constructor(command) { + super(); return extend(true, this, { queue: QueueAsync.new() }); } diff --git a/lib/visitors/formatter/json.es6 b/lib/visitors/formatter/json.es6 index 8bed669..d4505ed 100644 --- a/lib/visitors/formatter/json.es6 +++ b/lib/visitors/formatter/json.es6 @@ -12,6 +12,17 @@ import InterfaceException from 'util/exception/proxy/interface'; **/ class Json extends Visitor { + /** + * Constructor + * @public + * @override + * @param {Any} [...args] - constructor arguments + * @return {visitors.formatter.Json} + **/ + constructor(...args) { + return super(); + } + /** * Reducer Strategy to iterate over properties * @public diff --git a/lib/visualize/graph.es6 b/lib/visualize/graph.es6 new file mode 100644 index 0000000..22b6811 --- /dev/null +++ b/lib/visualize/graph.es6 @@ -0,0 +1,29 @@ +/** +* @module visualize +* @author Patricio Ferreira <3dimentionar@gmail.com> +**/ +import _ from 'underscore'; +import extend from 'extend'; +import Command from 'command'; + +/** +* Class Graph +* @extends {Command} +**/ +class Graph extends Command { + + /** + * Run + * @public + * @override + * @return {visualize.Graph} + **/ + run() { + // TODO + console.log('Graph.run()...'); + return super.run(); + } + +} + +export default Graph; diff --git a/lib/visualize/visualize.es6 b/lib/visualize/visualize.es6 deleted file mode 100644 index e69de29..0000000 diff --git a/mocha.opts b/mocha.opts index c567d39..c54a188 100644 --- a/mocha.opts +++ b/mocha.opts @@ -3,6 +3,6 @@ --require babel-polyfill --recursive --ui bdd ---timeout 200 +--timeout 300 --reporter spec --compilers es6:babel-register diff --git a/test/lib/bin/sqbox.spec.es6 b/test/lib/bin/sqbox.spec.es6 index 59eecdc..01a5ed1 100644 --- a/test/lib/bin/sqbox.spec.es6 +++ b/test/lib/bin/sqbox.spec.es6 @@ -2,34 +2,55 @@ * @module bin * @author Patricio Ferreira <3dimentionar@gmail.com> **/ +import SquareBox from 'bin/sqbox'; +import Commander from 'visitors/commander'; + describe('bin.SquareBox', function() { before(() => { + this.cwd = path.resolve(process.cwd()); this.sandbox = sinon.sandbox.create(); }); beforeEach(() => { - if(this.sqbox) { - this.mockProto = this.sandbox.mock(this.sqbox); - } + if(this.sqbox) this.mockProto = this.sandbox.mock(this.sqbox); + this.mockCommander = this.sandbox.mock(Commander.prototype); + this.input = [process.argv[0], this.cwd]; }); afterEach(() => { - if(this.mockProto) { - this.mockProto.verify(); - this.sandbox.restore(); - delete this.mockProto; - } + if(this.mockProto) this.mockProto.verify(); + this.mockCommander.verify(); + + this.sandbox.restore(); + + delete this.mockProto; + delete this.input; }); after(() => { + delete this.cwd; delete this.sandbox; }); describe('constructor()', () => { it('Should get an instance', () => { - this.sqbox = require('bin/sqbox'); + this.sqbox = require('bin/sqbox').default.new(); + assert.instanceOf(this.sqbox, SquareBox); + }); + + }); + + describe('static->run()', () => { + + it('Should run the command', () => { + this.input = this.input.concat(['sqbox']); + const expProgramArgv = this.mockCommander.expects('programArgv') + .once() + .returns(this.input); + + this.sqbox.run(); }); }); diff --git a/test/lib/visitors/commander.spec.es6 b/test/lib/visitors/commander.spec.es6 index e69de29..3e55151 100644 --- a/test/lib/visitors/commander.spec.es6 +++ b/test/lib/visitors/commander.spec.es6 @@ -0,0 +1 @@ +// TODO: Mock process.argv to trick yargs!!! From 76be995a5bf2b2aae892d5db0b7ce520122570bc Mon Sep 17 00:00:00 2001 From: kuakman <3dimentionar@gmail.com> Date: Tue, 14 Mar 2017 16:59:34 -0700 Subject: [PATCH 23/39] cli: checkpoint - visitors.Commander implementation completed. Progress on implementation for visitors.configuration.Local and visitors.configuration.Remote visited objects that will define configuration parsing and retrival. --- lib/bin/commands.json | 29 ++-- lib/bin/sqbox.es6 | 16 ++- lib/command.es6 | 74 +--------- lib/util/visitor/visitor.es6 | 2 +- lib/visitors/commander.es6 | 194 +++++++++++++++----------- lib/visitors/configuration.es6 | 128 ++++++++++++++--- lib/visitors/configuration/local.es6 | 58 ++++++++ lib/visitors/configuration/remote.es6 | 58 ++++++++ test/lib/bin/sqbox.spec.es6 | 2 +- 9 files changed, 375 insertions(+), 186 deletions(-) diff --git a/lib/bin/commands.json b/lib/bin/commands.json index 23aba0d..2894d8f 100644 --- a/lib/bin/commands.json +++ b/lib/bin/commands.json @@ -1,38 +1,41 @@ [{ "name": "help", - "aliases": ["h"], + "aliases": ["help"], "description": "Help", "path": "help/help", "options": { - "clean": { "alias": "c" }, - "bundle": { "alias": "b" }, - "graph": { "alias": "g" } + "clean": {}, + "bundle": {}, + "graph": {} } }, { "name": "clean", - "aliases": ["c"], + "aliases": ["clean"], "description": "Clean output directory", "path": "clean/clean", "options": { - "config": { "alias": "c", "default": ".sqboxrc" }, - "url": { "alias": "u" } + "override": { "default": false }, + "config": { "default": ".sqboxrc" }, + "url": {} } }, { "name": "bundle", - "aliases": ["b"], + "aliases": ["bundle"], "description": "Module Bundler", "path": "bundle/bundle", "options": { - "config": { "alias": "c", "default": ".sqboxrc" }, - "url": { "alias": "u" } + "override": { "default": false }, + "config": { "default": ".sqboxrc" }, + "url": {} } }, { "name": "graph", - "aliases": ["g"], + "aliases": ["graph"], "description": "Generates a graphical representation of your bundles", "path": "visualize/graph", "options": { - "config": { "alias": "c", "default": ".sqboxrc" }, - "url": { "alias": "u" } + "override": { "default": false }, + "config": { "default": ".sqboxrc" }, + "url": {} } }] diff --git a/lib/bin/sqbox.es6 b/lib/bin/sqbox.es6 index ea5239b..9018d71 100644 --- a/lib/bin/sqbox.es6 +++ b/lib/bin/sqbox.es6 @@ -36,7 +36,7 @@ class SquareBox extends Command { * @return {bin.SquareBox} **/ attachEvents() { - this.on(SquareBox.events.done, this.after); + this.once(SquareBox.events.done, this.after); return this; } @@ -44,11 +44,10 @@ class SquareBox extends Command { * Before Run * @public * @override - * @return {Object} + * @return {bin.SquareBox} **/ before() { - super.before().build(); - return this; + return super.before().build(); } /** @@ -59,7 +58,7 @@ class SquareBox extends Command { **/ run() { this.before(); - // TODO + this.parse(); return this; } @@ -115,6 +114,13 @@ class SquareBox extends Command { return instance.isPrivate(enforcer); } + /** + * Command options + * @static + * @type {Array} + **/ + static options = Command.options.concat(['options']); + /** * Static Run * @static diff --git a/lib/command.es6 b/lib/command.es6 index ac44a0d..fd16e11 100644 --- a/lib/command.es6 +++ b/lib/command.es6 @@ -36,7 +36,7 @@ class Command extends Visited { * @return {Command} **/ settings(options) { - return extend(true, this, this.constructor.defaults, _.pick(options, Command.options)); + return extend(true, this, this.constructor.defaults, _.pick(options, this.constructor.options)); } /** @@ -55,7 +55,7 @@ class Command extends Visited { * @return {command.Command} **/ acceptAll() { - return _.reduce(this.constructor.visitors, (memo, v) => memo.accept(Factory.get(v)), this); + return _.reduce(this.constructor.visitors, (memo, v) => memo.accept(Factory.get(v, this)), this); } /** @@ -120,69 +120,6 @@ class Command extends Visited { return this; } - /** - * Retrieves source - * @public - * @return {Object} - **/ - source() { - return this.source; - } - - /** - * Retrieves and resolves scan directory (glob) - * @public - * @return {String} - **/ - scan() { - return this.source().scan; - } - - /** - * Retrieves list of extensions to scan - * @public - * @return {Array} - **/ - extensions() { - return this.source().extensions; - } - - /** - * Retrieves aliases for modules - * @public - * @return {Object} - **/ - alias() { - return this.source().alias; - } - - /** - * Retrieves target - * @public - * @return {Object} - **/ - target() { - return this.target; - } - - /** - * Retrieves and resolves destination - * @public - * @return {String} - **/ - destination() { - return this.target().destination; - } - - /** - * Retrieves export format - * @public - * @return {String} - **/ - format() { - return this.target().format; - } - /** * Command Visitors * @static @@ -212,12 +149,7 @@ class Command extends Visited { static options = [ 'env', 'dirname', - 'cwd', - 'name', - 'aliases', - 'params', - 'description', - 'options' + 'cwd' ]; /** diff --git a/lib/util/visitor/visitor.es6 b/lib/util/visitor/visitor.es6 index d648cfa..34e0454 100644 --- a/lib/util/visitor/visitor.es6 +++ b/lib/util/visitor/visitor.es6 @@ -42,7 +42,7 @@ class Visitor extends EventEmitter { /** * Default Visit Strategy will return the visited object verbatim. - * This method is most likely to be overriden by subsclasses of this visitor. + * This method is likely to be overriden by subsclasses of this visitor when needed. * @public * @param {util.visitor.Visited} vi - instance to be visited by this visitor * @param {Any} [...args] - arguments passed to the vistior who visit the current visited instance diff --git a/lib/visitors/commander.es6 b/lib/visitors/commander.es6 index 60eeca3..fad8fca 100644 --- a/lib/visitors/commander.es6 +++ b/lib/visitors/commander.es6 @@ -2,13 +2,14 @@ * @module visitors * @author Patricio Ferreira <3dimentionar@gmail.com> **/ +import { resolve } from 'path'; import _ from 'underscore'; import extend from 'extend'; import yargs from 'yargs'; import chalk from 'chalk'; import Factory from 'util/factory/factory'; -import Collection from 'util/adt/collection'; import Visitor from 'util/visitor/visitor'; +import logger from 'util/logger/logger'; /** * Class Commander @@ -17,21 +18,35 @@ import Visitor from 'util/visitor/visitor'; class Commander extends Visitor { /** - * Adds Program Version + * Constructor * @public - * @param {String} version - program version - * @return {yargs} + * @override + * @param {Command} command - command visited by this visitor + * @return {util.visitor.Visitor} + **/ + constructor(command) { + return super({ command }); + } + + /** + * Default Strategy to add version option to the CLI + * @public + * @return {visitors.Commander} **/ _version() { - // FIXME: Get it from package.json - return yargs.version('1.0.0'); + try { + yargs.version(require(resolve(this.command.dirname, '..', 'package.json')).version); + } catch(ex) { + logger(`[WARN] SquareBox Version not detected caused by ${ex.message}`).debug(logger.yellow); + } + return this; } /** - * Adds Usage message + * Default Strategy to add usage to the CLI * @public * @param {String} [message = 'sqbox [args]'] - usage message to display - * @return {yargs} + * @return {visitors.Commander} **/ _usage(message = 'sqbox [args]') { yargs.usage(chalk.white(message)); @@ -39,80 +54,78 @@ class Commander extends Visitor { } /** - * Default Strategy that returns Command name using yargs convention format + * Default Strategy to add commands into the CLI via yargs * @private - * @param {Command} command - command instance - * @return {String} + * @return {visitors.Commander} **/ - _command(command) { - const { name, abbr } = command; - return chalk.green(`${name}` + (_.defined(abbr) ? `, -${abbr}` : '')); + _commands() { + this.command.constructor.commands.reduce(_.bind(this._new, this), yargs); + return this; } /** - * Default Strategy that returns Command's aliases + * Reducer appends a command into the memoized yargs reference * @public + * @param {yargs} memo - memoized yargs reference * @param {Command} command - command instance - * @return {Array} + * @return {yargs} **/ - _aliases(command) { - return command.aliases; + _new(memo, command) { + return memo.command(_.reduce(Commander.builder(), _.bind(this._create, this, command), {})); } /** - * Default Strategy that returns Command Description - * @private + * Build a new command object and + * @public * @param {Command} command - command instance - * @return {String} + * @param {Object} memo - memoized object + * @param {String} option - Yargs command option + * @return {Object} **/ - _desc(command) { - return chalk.yellow(command.description); + _create(command, memo, option) { + memo[option] = this[`_${option}`](command); + return memo; } /** - * Default Strategy that returns Command Options defaults + * Default Strategy that adds an epilogue information * @private - * @param {Command} command - command instance - * @return {Object} + * @return {visitors.Commander} **/ - _builder(command) { - return command.options; + _epilogue() { + yargs.epilogue(chalk.cyan(`For more information, please visit http://squarebox.nahuel.io/`)); + return this; } /** - * Default Strategy that returns Command Handler + * Default Strategy that controls yargs width used for displaying information * @private - * @param {util.visitor.Visited} ctx - context reference - * @return {Function} + * @return {visitors.Commander} **/ - _handler(command, ctx) { - const instance = Factory.register(command.path).get(command.path); - return _.bind(instance.run, instance); + _width() { + yargs.wrap(120); + return this; } /** - * Epilogue Information to show at the end - * @private - * @return {String} + * Default Strategy for yargs argument parsing and returns the reference to the visited + * @public + * @return {util.visitor.Visited} **/ - _epilogue() { - return chalk.cyan(`For more information about the configuration, please visit http://squarebox.nahuel.io/`); + _parse() { + yargs.parse(this.programArgv(), (err, argv, output) => + this.command.emit(Commander.events.parse, err, argv, output)); + return this.command; } /** * Parses Configuration options * @public - * @param {util.visitor.Visited} ctx - context reference - * @param {Function} parse - * @return {yargs} **/ - build(ctx, parse) { - const { commands } = ctx.constructor; + build() { yargs.reset(); - return commands.reduce(_.bind(this.command, this, ctx), this._usage()._version()) - .epilogue(this._epilogue()) - .wrap(120) - .parse(this.programArgv(), this.onParse); + return this._version()._usage()._commands()._epilogue()._width()._parse(); } /** @@ -127,41 +140,57 @@ class Commander extends Visitor { } /** - * Default Parse Handler - * @public - * @param {Error} [err] - error reference while parsing - * @param {Object} argv - arguments parsed - * @param {Object} output - output reference - * @return {visitors.Commander} + * Creates command nomenclature for yargs + * @private + * @param {Command} command - command instance + * @return {String} **/ - onParse(err, argv, output) { - return this; + _command(command) { + const { name, abbr } = command; + return chalk.green(`${name}` + (_.defined(abbr) ? `, -${abbr}` : '')); } /** - * Reducer appends a command into the memoized yargs reference + * Default Strategy that returns Command's aliases * @public - * @param {util.visitor.Visited} ctx - context reference - * @param {yargs} memo - memoized yargs reference * @param {Command} command - command instance - * @param {String} [description = ''] - command description - * @return {yargs} + * @return {Array} **/ - command(ctx, memo, command, description = '') { - return memo.command(_.reduce(Commander.builder(), _.bind(this.newCommand, this, command), {}, this)); + _aliases(command) { + return command.aliases; } /** - * Build a new command object and - * @public + * Default Strategy that returns Command Description + * @private + * @param {Command} command - command instance + * @return {String} + **/ + _desc(command) { + return chalk.yellow(command.description); + } + + /** + * Default Strategy that returns command options + * @private * @param {Command} command - command instance - * @param {Object} memo - memoized object - * @param {String} option - Yargs command option * @return {Object} **/ - newCommand(command, memo, option) { - memo[option] = this[`_${option}`](command); - return memo; + _builder(command) { + return command.options; + } + + /** + * Default Strategy that returns Command Handler + * @private + * @param {Command} command - command instance + * @return {Function} + **/ + _handler(command) { + return (argv) => { + Factory.register(command.path); + this.command.settings({ options: extend(false, _.omit(argv, Commander.ignore), { path: command.path }) }); + }; } /** @@ -174,23 +203,32 @@ class Commander extends Visitor { } /** - * Yargs Command Builder option names + * Commander Events * @static - * @return {Array} + * @type {Object} **/ - static builder() { - return ['command', 'aliases', 'desc', 'builder', 'handler']; + static events = { + /** + * @event read + **/ + parse: 'visitors:commander:read' } /** - * Move to Configuration - Available Artifacts + * Yargs arguments to ignore * @static * @type {Array} **/ - static artifacts = [ - 'visitors/configuration/remote', - 'visitors/configuration/local' - ]; + static ignore = ['$0', '_', 'version'] + + /** + * Yargs Command Builder option names + * @static + * @return {Array} + **/ + static builder() { + return ['command', 'aliases', 'desc', 'builder', 'handler']; + } } diff --git a/lib/visitors/configuration.es6 b/lib/visitors/configuration.es6 index 935774f..f6b196c 100644 --- a/lib/visitors/configuration.es6 +++ b/lib/visitors/configuration.es6 @@ -18,25 +18,99 @@ class Configuration extends Visitor { * Constructor * @public * @override - * @param {Command} commnand - command reference + * @param {Command} command - command visited by this visitor * @return {visitors.Configuration} **/ constructor(command) { - super(); - return extend(true, this, { queue: QueueAsync.new() }); + super({ command }); + return extend(true, this, { queue: QueueAsync.new([], { capacity: 2 }) }); } /** - * Load Configuration + * Create configuration retrieval method + * @private + * @param {On} + * @param {String} method - configuration method path + * @return {util.visitor.Visitor} + **/ + _create(options, method) { + this.queue.offer(Factory.get(method, this, options)); + } + + /** + * Parse Configuration based on source * @public - * @param {util.visitor.Visited} ctx - context reference - * @return {visitors.Commander} + * @return {Promise} **/ - load(ctx) { - // TODO: [js,json,uri] QueueAsync for executing for configuration + parse() { + Configuration.methods.forEach(_.bind(this._create, this, this.command.options)); + // Execute Poll return this; } + /** + * Retrieves source + * @public + * @return {Object} + **/ + source() { + return this.source; + } + + /** + * Retrieves and resolves scan directory (glob) + * @public + * @return {String} + **/ + scan() { + return this.source().scan; + } + + /** + * Retrieves list of extensions to scan + * @public + * @return {Array} + **/ + extensions() { + return this.source().extensions; + } + + /** + * Retrieves aliases for modules + * @public + * @return {Object} + **/ + alias() { + return this.source().alias; + } + + /** + * Retrieves target + * @public + * @return {Object} + **/ + target() { + return this.target; + } + + /** + * Retrieves and resolves destination + * @public + * @return {String} + **/ + destination() { + return this.target().destination; + } + + /** + * Retrieves export format + * @public + * @return {String} + **/ + format() { + return this.target().format; + } + /** * Visitor Name * @public @@ -61,19 +135,39 @@ class Configuration extends Visitor { }; /** - * Configuration options + * Factory * @static - * @type {Array} + * @type {util.factory.Factory} **/ - static options = [ - 'config', - 'source-scan', - 'source-extensions', - 'source-alias', - 'target-destination', - 'target-format' + static methods = [ + 'visitors/configuration/remote', + 'visitors/configuration/local' ]; + /** + * Configuration Events + * @static + * @type {Object} + **/ + static events = { + /** + * @event parse + **/ + parse: 'visitors:configuration:parse' + } + + /** + * Static Constructor + * @static + * @override + * @param {Any} [...args] - constructor arguments + * @return {visitors.Configuration} + **/ + static new(...args) { + Factory.registerAll(Configuration.methods); + return new this(...args); + } + } export default Configuration; diff --git a/lib/visitors/configuration/local.es6 b/lib/visitors/configuration/local.es6 index e69de29..ad4f57e 100644 --- a/lib/visitors/configuration/local.es6 +++ b/lib/visitors/configuration/local.es6 @@ -0,0 +1,58 @@ +/** +* @module visitors.configuration +* @author Patricio Ferreira <3dimentionar@gmail.com> +**/ +import _ from 'underscore'; +import extend from 'extend'; +import Visited from 'util/visitor/visited'; + +/** +* Class Local +* @extends {util.visitor.Visited} +* +* @uses {visitors.async.Asynchronous} +**/ +class Local extends Visited { + + /** + * Constructor + * @public + * @param {visitors.Configuration} configuration - configuration reference + * @param {Object} options - command options + * @return {visitors.configuration.Local} + **/ + constructor(configuration, options) { + return super(); + } + + /** + * Asynchronous next strategy + * @public + * @override + * @param adt {util.proxy.Asynchronous} adt used for asynchronous operations + * @param resolve {Function} asynchronous promise's resolve + * @param reject {Function} asynchronous promise's reject + * @return {Promise} + **/ + next(adt, resolve, reject) { + // TODO + return resolve(); + } + + /** + * Local options + * @static + * @type {Array} + **/ + static options = [ + 'config', + 'source-scan', + 'source-extensions', + 'source-alias', + 'target-destination', + 'target-format' + ]; + +} + +export default Local; diff --git a/lib/visitors/configuration/remote.es6 b/lib/visitors/configuration/remote.es6 index e69de29..1d4aa21 100644 --- a/lib/visitors/configuration/remote.es6 +++ b/lib/visitors/configuration/remote.es6 @@ -0,0 +1,58 @@ +/** +* @module visitors.configuration +* @author Patricio Ferreira <3dimentionar@gmail.com> +**/ +import _ from 'underscore'; +import extend from 'extend'; +import Visited from 'util/visitor/visited'; + +/** +* Class Remote +* @extends {util.visitor.Visited} +* +* @uses {visitors.async.Asynchronous} +**/ +class Remote extends Visited { + + /** + * Constructor + * @public + * @param {visitors.Configuration} configuration - configuration reference + * @param {Object} options - command options + * @return {visitors.configuration.Remote} + **/ + constructor(configuration, options) { + return super(); + } + + /** + * Asynchronous next strategy + * @public + * @override + * @param adt {util.proxy.Asynchronous} adt used for asynchronous operations + * @param resolve {Function} asynchronous promise's resolve + * @param reject {Function} asynchronous promise's reject + * @return {Promise} + **/ + next(adt, resolve, reject) { + // TODO + return resolve(); + } + + /** + * Remote options + * @static + * @type {Array} + **/ + static options = [ + 'config', + 'source-scan', + 'source-extensions', + 'source-alias', + 'target-destination', + 'target-format' + ]; + +} + +export default Remote; diff --git a/test/lib/bin/sqbox.spec.es6 b/test/lib/bin/sqbox.spec.es6 index 01a5ed1..c0a2442 100644 --- a/test/lib/bin/sqbox.spec.es6 +++ b/test/lib/bin/sqbox.spec.es6 @@ -45,7 +45,7 @@ describe('bin.SquareBox', function() { describe('static->run()', () => { it('Should run the command', () => { - this.input = this.input.concat(['sqbox']); + this.input = this.input.concat(['sqbox', 'bundle', '--url', 'http://squarebox.nahuel.io?profile=dev']); const expProgramArgv = this.mockCommander.expects('programArgv') .once() .returns(this.input); From f8bce9bacc73617a2b970445b31109d4b45e89e6 Mon Sep 17 00:00:00 2001 From: kuakman <3dimentionar@gmail.com> Date: Tue, 14 Mar 2017 17:57:54 -0700 Subject: [PATCH 24/39] cli: checkpoint - Progress on visitors.configuration.Local and Remote. --- lib/bin/sqbox.es6 | 25 +++++++++++++++- lib/util/mixins.es6 | 31 +++++++++++++++++++ lib/visitors/configuration.es6 | 21 +++++++++++-- lib/visitors/configuration/local.es6 | 43 ++++++++++++++++----------- lib/visitors/configuration/remote.es6 | 39 +++++++++++++++--------- 5 files changed, 123 insertions(+), 36 deletions(-) diff --git a/lib/bin/sqbox.es6 b/lib/bin/sqbox.es6 index 9018d71..e6dcfaf 100644 --- a/lib/bin/sqbox.es6 +++ b/lib/bin/sqbox.es6 @@ -8,6 +8,7 @@ import Collection from 'util/adt/collection'; import Factory from 'util/factory/factory'; import Command from 'command'; import CommandsList from './commands.json'; +import logger from 'util/logger/logger'; let enforcer = Symbol('SquareBox'); @@ -58,7 +59,29 @@ class SquareBox extends Command { **/ run() { this.before(); - this.parse(); + this.parse().then(_.bind(this.onParse, this)).catch(_.bind(this.onParseError, this)); + return this; + } + + /** + * Configuration Parse Complete Handler + * @public + * @param {Array} results - configuration results + * @return {bin.SquareBox} + **/ + onParse(results) { + // TODO: do this.options.override with results + return this; + } + + /** + * Configuration Parse Error Handler + * @public + * @param {Error} err - error reference + * @return {bin.SquareBox} + **/ + onParseError(err) { + logger(err.message).fatal(); return this; } diff --git a/lib/util/mixins.es6 b/lib/util/mixins.es6 index 9aad2bf..9a0b577 100644 --- a/lib/util/mixins.es6 +++ b/lib/util/mixins.es6 @@ -69,6 +69,37 @@ _.mixin({ return (_.isRealObject(o) || _.isArray(o)); }, + /** + * Returns true if the content is in json format, false otherwise. + * @public + * @param {String} content - content to be evaluated + * @return {Boolean} + **/ + isJson: function(content) { + try { + JSON.parse(content); + return true; + } catch(ex) { + return false; + } + }, + + /** + * Returns true if the content is in javascript format, false otherwise. + * @FIXME: Need to improve validation against javascript + * @public + * @param {String} content - content to be evaluated + * @return {Boolean} + **/ + isJS: function(content) { + try { + JSON.parse(content); + return false; + } catch(ex) { + return true; + } + }, + /** * Returns true if parameter source is an instance of parameter constructor, false otherwise. * @public diff --git a/lib/visitors/configuration.es6 b/lib/visitors/configuration.es6 index f6b196c..fe2f2ef 100644 --- a/lib/visitors/configuration.es6 +++ b/lib/visitors/configuration.es6 @@ -23,7 +23,7 @@ class Configuration extends Visitor { **/ constructor(command) { super({ command }); - return extend(true, this, { queue: QueueAsync.new([], { capacity: 2 }) }); + return extend(true, this, { queue: QueueAsync.new([], { capacity: Configuration.methods.length }) }); } /** @@ -44,8 +44,7 @@ class Configuration extends Visitor { **/ parse() { Configuration.methods.forEach(_.bind(this._create, this, this.command.options)); - // Execute Poll - return this; + return this.queue.poll(); } /** @@ -111,6 +110,22 @@ class Configuration extends Visitor { return this.target().format; } + /** + * Default Options + * @public + * @type {Array} + **/ + defaultOptions() { + return [ + 'config', + 'source-scan', + 'source-extensions', + 'source-alias', + 'target-destination', + 'target-format' + ]; + } + /** * Visitor Name * @public diff --git a/lib/visitors/configuration/local.es6 b/lib/visitors/configuration/local.es6 index ad4f57e..f04ba5e 100644 --- a/lib/visitors/configuration/local.es6 +++ b/lib/visitors/configuration/local.es6 @@ -2,7 +2,7 @@ * @module visitors.configuration * @author Patricio Ferreira <3dimentionar@gmail.com> **/ -import _ from 'underscore'; +import _ from 'util/mixins'; import extend from 'extend'; import Visited from 'util/visitor/visited'; @@ -22,7 +22,29 @@ class Local extends Visited { * @return {visitors.configuration.Local} **/ constructor(configuration, options) { - return super(); + return super({ configuration, options }); + } + + /** + * Returns true if local configuration exists, false otherwise + * @public + * @param {String} path - path to local configuration + * @return {Boolean} + **/ + exists() { + return true; + } + + /** + * Load local configuration file + * @public + * @param {String} name - file name + * @return {Object} + **/ + load(name) { + // TODO + console.log('Local.load()...'); + return true; } /** @@ -35,24 +57,9 @@ class Local extends Visited { * @return {Promise} **/ next(adt, resolve, reject) { - // TODO - return resolve(); + return resolve(this.load()); } - /** - * Local options - * @static - * @type {Array} - **/ - static options = [ - 'config', - 'source-scan', - 'source-extensions', - 'source-alias', - 'target-destination', - 'target-format' - ]; - } export default Local; diff --git a/lib/visitors/configuration/remote.es6 b/lib/visitors/configuration/remote.es6 index 1d4aa21..3dbe11d 100644 --- a/lib/visitors/configuration/remote.es6 +++ b/lib/visitors/configuration/remote.es6 @@ -2,8 +2,9 @@ * @module visitors.configuration * @author Patricio Ferreira <3dimentionar@gmail.com> **/ -import _ from 'underscore'; +import _ from 'util/mixins'; import extend from 'extend'; +import request from 'request'; import Visited from 'util/visitor/visited'; /** @@ -22,7 +23,19 @@ class Remote extends Visited { * @return {visitors.configuration.Remote} **/ constructor(configuration, options) { - return super(); + return super({ configuration, options }); + } + + /** + * Load remote configuration file + * @public + * @param {String} name - file name + * @return {Object} + **/ + load(name) { + // TODO + console.log('Remote.load()...'); + return this.emit(Remote.events.load, true); } /** @@ -35,23 +48,21 @@ class Remote extends Visited { * @return {Promise} **/ next(adt, resolve, reject) { - // TODO - return resolve(); + this.once(Remote.events.load, resolve); + return this.load(); } /** - * Remote options + * Remote Events * @static - * @type {Array} + * @type {Object} **/ - static options = [ - 'config', - 'source-scan', - 'source-extensions', - 'source-alias', - 'target-destination', - 'target-format' - ]; + static events = { + /** + * @event load + **/ + load: 'visitors:configuration:remote:load' + } } From 71108b6f1cc19afc01d60a13fbfa300252fe0227 Mon Sep 17 00:00:00 2001 From: kuakman <3dimentionar@gmail.com> Date: Wed, 15 Mar 2017 10:39:28 -0700 Subject: [PATCH 25/39] cli: checkpoint - Completed Implementation for visitors.configuration.Local and Remote to pull information from. Changes implementation of QueueAsync and StackAsync to return an array of results from the promise. Added json version of squarebox configuration and the javascript version. --- lib/bin/sqbox.es6 | 22 ++---- lib/util/adt/queue-async.es6 | 12 ++-- lib/util/adt/stack-async.es6 | 8 ++- lib/visitors/configuration.es6 | 38 ++++++++++- lib/visitors/configuration/local.es6 | 79 +++++++++++++++++++--- lib/visitors/configuration/remote.es6 | 62 ++++++++++++++--- test/lib/bin/sqbox.spec.es6 | 7 +- test/lib/util/adt/queue-async.spec.es6 | 9 ++- test/lib/util/adt/stack-async.spec.es6 | 9 ++- test/specs/.sqbox.js | 28 ++++++++ test/specs/{config-basic.json => .sqboxrc} | 0 11 files changed, 221 insertions(+), 53 deletions(-) create mode 100644 test/specs/.sqbox.js rename test/specs/{config-basic.json => .sqboxrc} (100%) diff --git a/lib/bin/sqbox.es6 b/lib/bin/sqbox.es6 index e6dcfaf..cdef8a3 100644 --- a/lib/bin/sqbox.es6 +++ b/lib/bin/sqbox.es6 @@ -59,29 +59,19 @@ class SquareBox extends Command { **/ run() { this.before(); - this.parse().then(_.bind(this.onParse, this)).catch(_.bind(this.onParseError, this)); + this.parse().then(_.bind(this.onConfiguration, this)); return this; } /** - * Configuration Parse Complete Handler + * Configuration Loaded and Parsed Handler * @public - * @param {Array} results - configuration results + * @param {visitors.Configuration} configuration - configuration visitor reference * @return {bin.SquareBox} **/ - onParse(results) { - // TODO: do this.options.override with results - return this; - } - - /** - * Configuration Parse Error Handler - * @public - * @param {Error} err - error reference - * @return {bin.SquareBox} - **/ - onParseError(err) { - logger(err.message).fatal(); + onConfiguration() { + const { path } = this.options; + // TODO: Factory.get(path, this.params()).run(); return this; } diff --git a/lib/util/adt/queue-async.es6 b/lib/util/adt/queue-async.es6 index 25daf52..f485e3c 100644 --- a/lib/util/adt/queue-async.es6 +++ b/lib/util/adt/queue-async.es6 @@ -65,9 +65,11 @@ class QueueAsync extends Queue { * @async * @override * @param {Object} [opts = {}] - additional options + * @param {Boolean} [next = false] - async queue already started * @return {Promise} **/ - async poll(opts = {}) { + async poll(opts = {}, next = false) { + if(!next) this._resetLast(); const res = await this.next(opts); return this.onNext(res, opts); } @@ -94,19 +96,19 @@ class QueueAsync extends Queue { **/ onNext(res, opts) { this._last.push(res); - return this.isEmpty() ? this.end(opts) : this.poll(opts); + return this.isEmpty() ? this.end(opts) : this.poll(opts, true); } /** - * Asynchronous Queue end + * Asynchronous Queue end will return last results. * @public * @emits {QueueAsync.events.end} - when opts.silent is false or undefined * @param {Object} [opts = {}] - additional options - * @return {util.adt.QueueAsync} + * @return {Array} **/ end(opts) { if(!opts.silent) this.emit(QueueAsync.events.end, this._last); - return this._resetLast(); + return this._last; } /** diff --git a/lib/util/adt/stack-async.es6 b/lib/util/adt/stack-async.es6 index a714504..bc69f46 100644 --- a/lib/util/adt/stack-async.es6 +++ b/lib/util/adt/stack-async.es6 @@ -65,9 +65,11 @@ class StackAsync extends Stack { * @async * @override * @param [opts = {}] {Object} additional options + * @param {Boolean} [next = false] - async queue already started * @return {Promise} **/ - async pop(opts = {}) { + async pop(opts = {}, next = false) { + if(!next) this._resetLast(); const res = await this.next(opts); return this.onNext(res, opts); } @@ -94,7 +96,7 @@ class StackAsync extends Stack { **/ onNext(res, opts) { this._last.push(res); - return this.isEmpty() ? this.end(opts) : this.pop(opts); + return this.isEmpty() ? this.end(opts) : this.pop(opts, true); } /** @@ -106,7 +108,7 @@ class StackAsync extends Stack { **/ end(opts) { if(!opts.silent) this.emit(StackAsync.events.end, this._last); - return this._resetLast(); + return this._last; } /** diff --git a/lib/visitors/configuration.es6 b/lib/visitors/configuration.es6 index fe2f2ef..8bc5d78 100644 --- a/lib/visitors/configuration.es6 +++ b/lib/visitors/configuration.es6 @@ -7,6 +7,7 @@ import extend from 'extend'; import Factory from 'util/factory/factory'; import Visitor from 'util/visitor/visitor'; import QueueAsync from 'util/adt/queue-async'; +import logger from 'util/logger/logger'; /** * Class Configuration @@ -34,7 +35,7 @@ class Configuration extends Visitor { * @return {util.visitor.Visitor} **/ _create(options, method) { - this.queue.offer(Factory.get(method, this, options)); + this.queue.offer(Factory.get(method, this.command, options)); } /** @@ -44,7 +45,40 @@ class Configuration extends Visitor { **/ parse() { Configuration.methods.forEach(_.bind(this._create, this, this.command.options)); - return this.queue.poll(); + return this.queue.poll().then(_.bind(this.onParse, this)); + } + + /** + * Configuration Parse Complete Handler + * @public + * @param {Array} results - configuration results + * @return {visitors.Configuration} + **/ + onParse(results) { + _.each(_.compact(results), this.onOptions, this); + return this; + } + + /** + * Configuration options handler based on results + * @public + * @param {Object} result - result + * @return + **/ + onOptions(result) { + if(_.defined(result.warn)) return this.onParseError(result.warn); + return this; + } + + /** + * Configuration Parse Error Handler + * @public + * @param {String} message - error message reference + * @return {visitors.Configuration} + **/ + onParseError(message) { + logger(message).warn(); + return this; } /** diff --git a/lib/visitors/configuration/local.es6 b/lib/visitors/configuration/local.es6 index f04ba5e..59ba7e0 100644 --- a/lib/visitors/configuration/local.es6 +++ b/lib/visitors/configuration/local.es6 @@ -2,9 +2,12 @@ * @module visitors.configuration * @author Patricio Ferreira <3dimentionar@gmail.com> **/ +import fs from 'fs-extra'; +import { resolve } from 'path'; import _ from 'util/mixins'; import extend from 'extend'; import Visited from 'util/visitor/visited'; +import logger from 'util/logger/logger'; /** * Class Local @@ -17,22 +20,67 @@ class Local extends Visited { /** * Constructor * @public - * @param {visitors.Configuration} configuration - configuration reference + * @param {Command} command - command reference * @param {Object} options - command options * @return {visitors.configuration.Local} **/ - constructor(configuration, options) { - return super({ configuration, options }); + constructor(command, options) { + return super({ command, options }); + } + + /** + * Resolves absolute path to a given file + * @public + * @param {String} file - file name to evaluate + * @return {String} + **/ + resolvePath(file) { + return resolve(this.command.cwd, file); } /** * Returns true if local configuration exists, false otherwise * @public - * @param {String} path - path to local configuration + * @param {String} file - file name to evaluate * @return {Boolean} **/ - exists() { - return true; + exists(file) { + try { + return _.defined(file) && _.defined(fs.statSync(file)); + } catch(ex) { + // TODO: logger.debug(); + return false; + } + } + + /** + * Will try to open configuration as a json + * @public + * @param {String} path - resolved path to configuration + * @return {Object} + **/ + tryJson(path) { + try { + return fs.readJsonSync(path); + } catch(ex) { + // TODO: logger.debug(); + return null; + } + } + + /** + * Will try to open configuration as javascript via module.export + * @public + * @param {String} path - resolved path to configuration + * @return {Object} + **/ + tryJs(path) { + try { + return eval(fs.readFileSync(path, { encoding: 'utf8' }).toString()); + } catch(ex) { + // TODO: logger.debug(); + return null; + } } /** @@ -42,9 +90,10 @@ class Local extends Visited { * @return {Object} **/ load(name) { - // TODO - console.log('Local.load()...'); - return true; + let file = this.resolvePath(name), output = ''; + if(this.exists(file) && (output = (this.tryJson(file) || this.tryJs(file)))) return output; + // logger.debug() + return { warn: Local.messages.notFound }; } /** @@ -57,7 +106,17 @@ class Local extends Visited { * @return {Promise} **/ next(adt, resolve, reject) { - return resolve(this.load()); + const { config } = this.options; + return resolve(this.load(config)); + } + + /** + * Local Configuration Messages + * @static + * @type {Object} + **/ + static messages = { + notFound: `Local configuration not found` } } diff --git a/lib/visitors/configuration/remote.es6 b/lib/visitors/configuration/remote.es6 index 3dbe11d..cffb916 100644 --- a/lib/visitors/configuration/remote.es6 +++ b/lib/visitors/configuration/remote.es6 @@ -18,24 +18,55 @@ class Remote extends Visited { /** * Constructor * @public - * @param {visitors.Configuration} configuration - configuration reference + * @param {Command} command - command reference * @param {Object} options - command options * @return {visitors.configuration.Remote} **/ - constructor(configuration, options) { - return super({ configuration, options }); + constructor(command, options) { + return super({ command, options }); } /** - * Load remote configuration file + * Load remote configuration file from url * @public - * @param {String} name - file name + * @param {String} url - url * @return {Object} **/ - load(name) { - // TODO - console.log('Remote.load()...'); - return this.emit(Remote.events.load, true); + load(url) { + request(url, _.bind(this.onLoad, this)); + return this; + } + + /** + * Remote Load Handler + * @public + * @param {Error} [err] - request error + * @param {Object} response - request response reference + * @param {String} body - request error + * @return {Boolean} + **/ + onLoad(err, response, body) { + let output = {}; + try { + if(_.defined(err)) + return this.out({ warn: Remote.messages.error({ warn: err }) }); + if(response.statusCode !== 200) + return this.out({ warn: Remote.messages.error({ warn: response.statusCode }) }); + this.out(JSON.parse(body)); + } catch(ex) { + // logger.debug(); + this.out({ warn: (Remote.messages.invalid + ' - ' + ex.message) }); + } + } + + /** + * Result Output + * @public + * @param {Object} output - output to dispatch + * @return {Boolean} + **/ + out(output) { + return this.emit(Remote.events.load, output); } /** @@ -48,8 +79,9 @@ class Remote extends Visited { * @return {Promise} **/ next(adt, resolve, reject) { + const { url } = this.options; this.once(Remote.events.load, resolve); - return this.load(); + return _.defined(url) ? this.load(url) : this.out(null); } /** @@ -64,6 +96,16 @@ class Remote extends Visited { load: 'visitors:configuration:remote:load' } + /** + * Remote Configuration Messages + * @static + * @type {Object} + **/ + static messages = { + error: _.template(`Remote Configuration - <%= warn %>`), + invalid: `Invalid JSON format` + } + } export default Remote; diff --git a/test/lib/bin/sqbox.spec.es6 b/test/lib/bin/sqbox.spec.es6 index c0a2442..a7ecc83 100644 --- a/test/lib/bin/sqbox.spec.es6 +++ b/test/lib/bin/sqbox.spec.es6 @@ -45,7 +45,12 @@ describe('bin.SquareBox', function() { describe('static->run()', () => { it('Should run the command', () => { - this.input = this.input.concat(['sqbox', 'bundle', '--url', 'http://squarebox.nahuel.io?profile=dev']); + this.input = this.input.concat([ + 'sqbox', + 'bundle', + //'--url', 'http://squarebox.nahuel.io?profile=dev', + '--config', 'test/specs/.sqboxrc' + ]); const expProgramArgv = this.mockCommander.expects('programArgv') .once() .returns(this.input); diff --git a/test/lib/util/adt/queue-async.spec.es6 b/test/lib/util/adt/queue-async.spec.es6 index 130fa2b..507dec4 100644 --- a/test/lib/util/adt/queue-async.spec.es6 +++ b/test/lib/util/adt/queue-async.spec.es6 @@ -73,9 +73,12 @@ describe('util.adt.QueueAsync', function() { assert.isTrue(exp.offer({ env: 'dev' }, { silent: true })); assert.isTrue(exp.offer({ env: 'stage' }, { silent: true })); - const result = exp.poll({ silent: true }).then((queue) => { - assert.instanceOf(queue, QueueAsync); - assert.isTrue(queue.isEmpty()); + const result = exp.poll({ silent: true }).then((results) => { + assert.instanceOf(results, Array); + assert.lengthOf(results, 2); + assert.instanceOf(results[0], Command); + assert.isTrue(exp.isEmpty()); + done(); }).catch((err) => console.log(err)); }); diff --git a/test/lib/util/adt/stack-async.spec.es6 b/test/lib/util/adt/stack-async.spec.es6 index a5df7ae..0a76575 100644 --- a/test/lib/util/adt/stack-async.spec.es6 +++ b/test/lib/util/adt/stack-async.spec.es6 @@ -73,9 +73,12 @@ describe('util.adt.StackAsync', function() { assert.isTrue(exp.push({ env: 'dev' }, { silent: true })); assert.isTrue(exp.push({ env: 'stage' }, { silent: true })); - const result = exp.pop({ silent: true }).then((stack) => { - assert.instanceOf(stack, StackAsync); - assert.isTrue(stack.isEmpty()); + const result = exp.pop({ silent: true }).then((results) => { + assert.instanceOf(results, Array); + assert.lengthOf(results, 2); + assert.instanceOf(results[0], Command); + assert.isTrue(exp.isEmpty()); + done(); }).catch((err) => console.log(err)); }); diff --git a/test/specs/.sqbox.js b/test/specs/.sqbox.js new file mode 100644 index 0000000..a4ea5a0 --- /dev/null +++ b/test/specs/.sqbox.js @@ -0,0 +1,28 @@ +/** +* Config Example using module.exports +* @author Patricio Ferreira <3dimentionar@gmail.com> +**/ +module.exports = { + source: { + scan: './src/**', + extensions: ['.js', '.es6', '.es'], + alias: { + common: 'shared/common', + libraries: 'libs' + } + }, + target: [{ + destination: './dist/global', + format: 'ifie' + }, { + destination: './dist/cjs', + format: 'cjs' + }, { + destination: './dist/umd', + format: 'umd' + }, { + destination: './dist/amd', + format: 'amd' + }], + logLevel: 'debug' +}; diff --git a/test/specs/config-basic.json b/test/specs/.sqboxrc similarity index 100% rename from test/specs/config-basic.json rename to test/specs/.sqboxrc From 69224571d55df3a9d6a8d1bd24c1d6c45d3f5aac Mon Sep 17 00:00:00 2001 From: kuakman <3dimentionar@gmail.com> Date: Wed, 15 Mar 2017 10:43:10 -0700 Subject: [PATCH 26/39] cli: checkpoint - Bumped up mocha timeout to 2000 millisecons. SquareBox unit test is taking longer than normal. Future investigation over Proxy usage. --- mocha.opts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mocha.opts b/mocha.opts index c54a188..a9837db 100644 --- a/mocha.opts +++ b/mocha.opts @@ -3,6 +3,6 @@ --require babel-polyfill --recursive --ui bdd ---timeout 300 +--timeout 2000 --reporter spec --compilers es6:babel-register From 37002c3e60b3085d491fb967b4945436529f261a Mon Sep 17 00:00:00 2001 From: kuakman <3dimentionar@gmail.com> Date: Wed, 15 Mar 2017 15:06:01 -0700 Subject: [PATCH 27/39] cli: checkpoint - Completed Initial implementation of configuration parsing. Replaced esprima by acorn. Change specs for target CLI option. --- lib/bin/commands.json | 18 +- lib/command.es6 | 61 +++++- lib/visitors/configuration.es6 | 173 +++++++++--------- .../configuration/formatter/alias.es6 | 15 ++ .../configuration/formatter/extensions.es6 | 15 ++ .../configuration/formatter/target.es6 | 22 +++ package.json | 2 +- test/lib/bin/sqbox.spec.es6 | 9 +- test/specs/.sqbox.js | 31 ++-- test/specs/.sqboxrc | 31 ++-- 10 files changed, 256 insertions(+), 121 deletions(-) create mode 100644 lib/visitors/configuration/formatter/alias.es6 create mode 100644 lib/visitors/configuration/formatter/extensions.es6 create mode 100644 lib/visitors/configuration/formatter/target.es6 diff --git a/lib/bin/commands.json b/lib/bin/commands.json index 2894d8f..40d8d84 100644 --- a/lib/bin/commands.json +++ b/lib/bin/commands.json @@ -16,7 +16,8 @@ "options": { "override": { "default": false }, "config": { "default": ".sqboxrc" }, - "url": {} + "url": {}, + "target": { "default": "./dist" } } }, { "name": "bundle", @@ -26,7 +27,17 @@ "options": { "override": { "default": false }, "config": { "default": ".sqboxrc" }, - "url": {} + "url": {}, + "scan": { "default": "." }, + "extensions": { "default": [".js", ".jsx", ".es6", ".es"] }, + "alias": { "default": {} }, + "target": { + "default": [{ + "destination": "./dist", + "format": "umd" + }] + }, + "logLevel": { "default": "output" } } }, { "name": "graph", @@ -36,6 +47,7 @@ "options": { "override": { "default": false }, "config": { "default": ".sqboxrc" }, - "url": {} + "url": {}, + "target": { "default": "./dist" } } }] diff --git a/lib/command.es6 b/lib/command.es6 index fd16e11..2cb1299 100644 --- a/lib/command.es6 +++ b/lib/command.es6 @@ -120,6 +120,61 @@ class Command extends Visited { return this; } + /** + * Retrieves source + * @public + * @return {Object} + **/ + source() { + return this.source; + } + + /** + * Retrieves and resolves scan directory (glob) + * @public + * @return {String} + **/ + scan() { + return this.source().scan; + } + + /** + * Retrieves list of extensions to scan + * @public + * @return {Array} + **/ + extensions() { + return this.source().extensions; + } + + /** + * Retrieves aliases for modules + * @public + * @return {Object} + **/ + alias() { + return this.source().alias; + } + + /** + * Retrieves target + * @public + * @return {Object} + **/ + target() { + return this.target; + } + + /** + * Retrieve targets by using a given predicate passed by parameter + * @public + * @param {Function} predicate - predicate to walk over the targets + * @return {Array} + **/ + targets(predicate) { + return _.defined(predicate) && _.isFunction(predicate) ? _.map(this.target, predicate, this) : []; + } + /** * Command Visitors * @static @@ -149,7 +204,11 @@ class Command extends Visited { static options = [ 'env', 'dirname', - 'cwd' + 'cwd', + 'scan', + 'extensions', + 'alias', + 'target' ]; /** diff --git a/lib/visitors/configuration.es6 b/lib/visitors/configuration.es6 index 8bc5d78..2506503 100644 --- a/lib/visitors/configuration.es6 +++ b/lib/visitors/configuration.es6 @@ -7,7 +7,7 @@ import extend from 'extend'; import Factory from 'util/factory/factory'; import Visitor from 'util/visitor/visitor'; import QueueAsync from 'util/adt/queue-async'; -import logger from 'util/logger/logger'; +import logger, { Logger } from 'util/logger/logger'; /** * Class Configuration @@ -39,125 +39,120 @@ class Configuration extends Visitor { } /** - * Parse Configuration based on source - * @public - * @return {Promise} - **/ - parse() { - Configuration.methods.forEach(_.bind(this._create, this, this.command.options)); - return this.queue.poll().then(_.bind(this.onParse, this)); - } - - /** - * Configuration Parse Complete Handler - * @public - * @param {Array} results - configuration results + * Format and overrides command options from the CLI arguments + * @private + * @param {Object} opts - cli options for source * @return {visitors.Configuration} **/ - onParse(results) { - _.each(_.compact(results), this.onOptions, this); + _override(opts) { + let toOverride = _.pick(opts, Configuration.cliOptions); + extend(true, this.command, _.reduce(toOverride, this._format, toOverride, this)); return this; } /** - * Configuration options handler based on results + * Format a given CLI option by evaluating the key to load the factory formatter and passing the value + * to the formatter. * @public - * @param {Object} result - result + * @param {Object} memo - options being memoized + * @param {Any} memo - options being memoized + * @param {String} memo - options being memoized * @return **/ - onOptions(result) { - if(_.defined(result.warn)) return this.onParseError(result.warn); - return this; + _format(memo, value, key) { + let ph = this.formatterPath(key); + if(Factory.exists(ph)) memo[key] = Factory.get(ph, value); + return memo; } /** - * Configuration Parse Error Handler - * @public - * @param {String} message - error message reference + * Merge configuration options (and possibly) applies overrides by CLI arguments. + * @private + * @param {Object} opts - configuration options for target * @return {visitors.Configuration} **/ - onParseError(message) { - logger(message).warn(); + _source(opts) { + extend(true, this.command, _.pick(opts, Configuration.cliOptions)) return this; } /** - * Retrieves source - * @public - * @return {Object} + * Merge configuration options (and possibly) applies overrides by CLI arguments. + * @private + * @param {Object} opts - configuration options for target + * @return {visitors.Configuration} **/ - source() { - return this.source; + _target(opts) { + extend(true, this.command, _.pick(opts, Configuration.cliOptions)); + return this; } /** - * Retrieves and resolves scan directory (glob) - * @public - * @return {String} + * Sets Logger level + * @private + * @param {String} level - logger level + * @return {visitors.Configuration} **/ - scan() { - return this.source().scan; + _logger(level) { + logger.level(Logger.level[level]); + return this; } /** - * Retrieves list of extensions to scan + * Parse Configuration based on source * @public - * @return {Array} + * @return {Promise} **/ - extensions() { - return this.source().extensions; + parse() { + Configuration.methods.forEach(_.bind(this._create, this, this.command.options)); + return this.queue.poll().then(_.bind(this.onParse, this)); } /** - * Retrieves aliases for modules + * Configuration Parse Complete Handler * @public - * @return {Object} + * @param {Array} results - configuration results + * @return {visitors.Configuration} **/ - alias() { - return this.source().alias; + onParse(results) { + _.each(_.compact(results), this.onOptions, this); + return this; } /** - * Retrieves target + * Configuration options handler based on results * @public - * @return {Object} + * @param {Object} result - result + * @return **/ - target() { - return this.target; + onOptions(result) { + if(_.defined(result.warn)) return this.onParseError(result.warn); + this._source(result.source) + ._target(result.target) + ._logger(result.logLevel) + ._override(this.command.options); + return this; } /** - * Retrieves and resolves destination + * Configuration Parse Error Handler * @public - * @return {String} + * @param {String} message - error message reference + * @return {visitors.Configuration} **/ - destination() { - return this.target().destination; + onParseError(message) { + logger(message).warn(); + return this; } /** - * Retrieves export format + * Resolves and return full formatter path * @public + * @param {String} name - formatter name * @return {String} **/ - format() { - return this.target().format; - } - - /** - * Default Options - * @public - * @type {Array} - **/ - defaultOptions() { - return [ - 'config', - 'source-scan', - 'source-extensions', - 'source-alias', - 'target-destination', - 'target-format' - ]; + formatterPath(name) { + return `visitors/configuration/formatter/${name}`; } /** @@ -169,20 +164,6 @@ class Configuration extends Visitor { return 'ConfigurationVisitor'; } - /** - * Configuration Defaults - * @static - * @type {Object} - **/ - static defaults = { - 'config': '.sqboxrc', - 'source-scan': '.', - 'source-extensions': ['.js', '.jsx', '.es6', '.es'], - 'source-alias': {}, - 'target-destination': './dist', - 'target-format': 'ifie' - }; - /** * Factory * @static @@ -193,6 +174,24 @@ class Configuration extends Visitor { 'visitors/configuration/local' ]; + /** + * Factory Formatters for Options + * @static + * @type {Array} + **/ + static formatters = [ + 'visitors/configuration/formatter/alias', + 'visitors/configuration/formatter/extensions', + 'visitors/configuration/formatter/target' + ]; + + /** + * CLI Argument Options + * @static + * @type {Array} + **/ + static cliOptions = ['scan', 'extensions', 'alias', 'target']; + /** * Configuration Events * @static @@ -203,7 +202,7 @@ class Configuration extends Visitor { * @event parse **/ parse: 'visitors:configuration:parse' - } + }; /** * Static Constructor @@ -213,7 +212,7 @@ class Configuration extends Visitor { * @return {visitors.Configuration} **/ static new(...args) { - Factory.registerAll(Configuration.methods); + Factory.registerAll(Configuration.methods.concat(Configuration.formatters)); return new this(...args); } diff --git a/lib/visitors/configuration/formatter/alias.es6 b/lib/visitors/configuration/formatter/alias.es6 new file mode 100644 index 0000000..05ca73c --- /dev/null +++ b/lib/visitors/configuration/formatter/alias.es6 @@ -0,0 +1,15 @@ +/** +* @module visitors.configuration.formatter +* @author Patricio Ferreira <3dimentionar@gmail.com> +**/ +import _ from 'underscore'; + +/** +* Alias Formatter +* @static +* @param {String} [input = ''] - input to format +* @return {Object} +**/ +export default (input = '') => { + return _.chain(input.split(',')).invoke('split', ':').object().value(); +}; diff --git a/lib/visitors/configuration/formatter/extensions.es6 b/lib/visitors/configuration/formatter/extensions.es6 new file mode 100644 index 0000000..005d33a --- /dev/null +++ b/lib/visitors/configuration/formatter/extensions.es6 @@ -0,0 +1,15 @@ +/** +* @module visitors.configuration.formatter +* @author Patricio Ferreira <3dimentionar@gmail.com> +**/ +import _ from 'underscore'; + +/** +* Extensions Formatter +* @static +* @param {String} [input = ''] - input to format +* @return {Object} +**/ +export default (input = '') => { + return input.split(','); +}; diff --git a/lib/visitors/configuration/formatter/target.es6 b/lib/visitors/configuration/formatter/target.es6 new file mode 100644 index 0000000..169a7da --- /dev/null +++ b/lib/visitors/configuration/formatter/target.es6 @@ -0,0 +1,22 @@ +/** +* @module visitors.configuration.formatter +* @author Patricio Ferreira <3dimentionar@gmail.com> +**/ +import _ from 'underscore'; + +/** +* Target Formatter +* @static +* @param {String} [input = ''] - input to format +* @return {Object} +**/ +export default (input = '') => { + return _.chain(input.split(',')) + .invoke('split', '>') + .reduce((m, v, k) => { + let ps = v[1].split(':'); + m[v[0]] = { destination: ps[1], format: ps[0] }; + return m; + }, {}) + .value(); +}; diff --git a/package.json b/package.json index 75b5e62..9066c8f 100644 --- a/package.json +++ b/package.json @@ -48,13 +48,13 @@ "sinon": "1.17.7" }, "dependencies": { + "acorn": "4.0.11", "babel-plugin-module-resolver": "2.5.0", "babel-polyfill": "6.23.0", "babel-preset-es2015": "6.22.0", "babel-preset-stage-2": "6.22.0", "babel-register": "6.23.0", "chalk": "1.1.3", - "esprima": "3.1.3", "extend": "3.0.0", "fs-extra": "2.0.0", "glob": "7.1.1", diff --git a/test/lib/bin/sqbox.spec.es6 b/test/lib/bin/sqbox.spec.es6 index a7ecc83..d887897 100644 --- a/test/lib/bin/sqbox.spec.es6 +++ b/test/lib/bin/sqbox.spec.es6 @@ -48,14 +48,17 @@ describe('bin.SquareBox', function() { this.input = this.input.concat([ 'sqbox', 'bundle', - //'--url', 'http://squarebox.nahuel.io?profile=dev', - '--config', 'test/specs/.sqboxrc' + '--config', 'test/specs/.sqboxrc', + '--scan', './source/**', + '--extensions', '.js,.es6', + '--alias', 'common:./path/common', + '--target', 'add>umd:./dist/umd,other>cjs:./dist/cjs' ]); const expProgramArgv = this.mockCommander.expects('programArgv') .once() .returns(this.input); - this.sqbox.run(); + assert.instanceOf(this.sqbox.run(), SquareBox); }); }); diff --git a/test/specs/.sqbox.js b/test/specs/.sqbox.js index a4ea5a0..b606731 100644 --- a/test/specs/.sqbox.js +++ b/test/specs/.sqbox.js @@ -11,18 +11,23 @@ module.exports = { libraries: 'libs' } }, - target: [{ - destination: './dist/global', - format: 'ifie' - }, { - destination: './dist/cjs', - format: 'cjs' - }, { - destination: './dist/umd', - format: 'umd' - }, { - destination: './dist/amd', - format: 'amd' - }], + target: { + global: { + destination: './dist/global', + format: 'ifie' + }, + umd: { + destination: './dist/umd', + format: 'umd' + }, + cjs: { + destination: './dist/cjs', + format: 'cjs' + }, + amd: { + destination: './dist/amd', + format: 'amd' + } + }, logLevel: 'debug' }; diff --git a/test/specs/.sqboxrc b/test/specs/.sqboxrc index 4096546..0bc3059 100644 --- a/test/specs/.sqboxrc +++ b/test/specs/.sqboxrc @@ -7,18 +7,23 @@ "libraries": "libs" } }, - "target": [{ - "destination": "./dist/global", - "format": "ifie" - }, { - "destination": "./dist/cjs", - "format": "cjs" - }, { - "destination": "./dist/umd", - "format": "umd" - }, { - "destination": "./dist/amd", - "format": "amd" - }], + "target": { + "global": { + "destination": "./dist/global", + "format": "ifie" + }, + "umd": { + "destination": "./dist/umd", + "format": "umd" + }, + "cjs": { + "destination": "./dist/cjs", + "format": "cjs" + }, + "amd": { + "destination": "./dist/amd", + "format": "amd" + } + }, "logLevel": "debug" } From 63fee61ba9904d67e94adcce773f00cd48ed5e1e Mon Sep 17 00:00:00 2001 From: kuakman <3dimentionar@gmail.com> Date: Fri, 17 Mar 2017 07:13:24 -0700 Subject: [PATCH 28/39] cli: checkpoint - Added Exclude configuration option for scanning source paths. Added visitors.configuration.fomatter.Exclude. Added aliases to CLI arguments. --- lib/bin/commands.json | 18 +++++++----------- lib/command.es6 | 10 ++++++++++ lib/visitors/configuration.es6 | 6 ++++-- .../configuration/formatter/exclude.es6 | 15 +++++++++++++++ test/lib/bin/sqbox.spec.es6 | 9 +++++---- test/specs/.sqbox.js | 1 + test/specs/.sqboxrc | 1 + 7 files changed, 43 insertions(+), 17 deletions(-) create mode 100644 lib/visitors/configuration/formatter/exclude.es6 diff --git a/lib/bin/commands.json b/lib/bin/commands.json index 40d8d84..e4301ca 100644 --- a/lib/bin/commands.json +++ b/lib/bin/commands.json @@ -27,17 +27,13 @@ "options": { "override": { "default": false }, "config": { "default": ".sqboxrc" }, - "url": {}, - "scan": { "default": "." }, - "extensions": { "default": [".js", ".jsx", ".es6", ".es"] }, - "alias": { "default": {} }, - "target": { - "default": [{ - "destination": "./dist", - "format": "umd" - }] - }, - "logLevel": { "default": "output" } + "url": { "alias": "u" }, + "scan": { "alias": "s", "default": "." }, + "exclude": { "alias": "x" }, + "extensions": { "alias": "e", "default": ".js,.jsx,.es6,.es" }, + "alias": { "alias": "a", "default": {} }, + "target": { "alias": "t", "default": "dist>umd:./dist" }, + "logLevel": { "alias": "lv", "default": "output" } } }, { "name": "graph", diff --git a/lib/command.es6 b/lib/command.es6 index 2cb1299..f685ac6 100644 --- a/lib/command.es6 +++ b/lib/command.es6 @@ -138,6 +138,15 @@ class Command extends Visited { return this.source().scan; } + /** + * Retrieves and resolves excluded folders + * @public + * @return {Array} + **/ + exclude() { + return this.source().exclude; + } + /** * Retrieves list of extensions to scan * @public @@ -206,6 +215,7 @@ class Command extends Visited { 'dirname', 'cwd', 'scan', + 'exclude', 'extensions', 'alias', 'target' diff --git a/lib/visitors/configuration.es6 b/lib/visitors/configuration.es6 index 2506503..f765ee9 100644 --- a/lib/visitors/configuration.es6 +++ b/lib/visitors/configuration.es6 @@ -105,7 +105,7 @@ class Configuration extends Visitor { **/ parse() { Configuration.methods.forEach(_.bind(this._create, this, this.command.options)); - return this.queue.poll().then(_.bind(this.onParse, this)); + return this.queue.poll().then(_.bind(this.onParse, this)).catch(_.bind(this.onParseError, this)); } /** @@ -131,6 +131,7 @@ class Configuration extends Visitor { ._target(result.target) ._logger(result.logLevel) ._override(this.command.options); + console.log(this.command); return this; } @@ -181,6 +182,7 @@ class Configuration extends Visitor { **/ static formatters = [ 'visitors/configuration/formatter/alias', + 'visitors/configuration/formatter/exclude', 'visitors/configuration/formatter/extensions', 'visitors/configuration/formatter/target' ]; @@ -190,7 +192,7 @@ class Configuration extends Visitor { * @static * @type {Array} **/ - static cliOptions = ['scan', 'extensions', 'alias', 'target']; + static cliOptions = ['scan', 'exclude', 'extensions', 'alias', 'target']; /** * Configuration Events diff --git a/lib/visitors/configuration/formatter/exclude.es6 b/lib/visitors/configuration/formatter/exclude.es6 new file mode 100644 index 0000000..4009dd4 --- /dev/null +++ b/lib/visitors/configuration/formatter/exclude.es6 @@ -0,0 +1,15 @@ +/** +* @module visitors.configuration.formatter +* @author Patricio Ferreira <3dimentionar@gmail.com> +**/ +import _ from 'underscore'; + +/** +* Exclude Formatter +* @static +* @param {String} [input = ''] - input to format +* @return {Object} +**/ +export default (input = '') => { + return input.split(','); +}; diff --git a/test/lib/bin/sqbox.spec.es6 b/test/lib/bin/sqbox.spec.es6 index d887897..4234c58 100644 --- a/test/lib/bin/sqbox.spec.es6 +++ b/test/lib/bin/sqbox.spec.es6 @@ -49,10 +49,11 @@ describe('bin.SquareBox', function() { 'sqbox', 'bundle', '--config', 'test/specs/.sqboxrc', - '--scan', './source/**', - '--extensions', '.js,.es6', - '--alias', 'common:./path/common', - '--target', 'add>umd:./dist/umd,other>cjs:./dist/cjs' + '--s', './source/**', + '--x', './source/dependencies/**,./source/package/**', + '--e', '.js,.es6', + '--a', 'common:./path/common', + '--t', 'add>umd:./dist/umd,other>cjs:./dist/cjs' ]); const expProgramArgv = this.mockCommander.expects('programArgv') .once() diff --git a/test/specs/.sqbox.js b/test/specs/.sqbox.js index b606731..d78e20d 100644 --- a/test/specs/.sqbox.js +++ b/test/specs/.sqbox.js @@ -5,6 +5,7 @@ module.exports = { source: { scan: './src/**', + exclude: ['./src/dependencies/**'], extensions: ['.js', '.es6', '.es'], alias: { common: 'shared/common', diff --git a/test/specs/.sqboxrc b/test/specs/.sqboxrc index 0bc3059..2d6e97e 100644 --- a/test/specs/.sqboxrc +++ b/test/specs/.sqboxrc @@ -1,6 +1,7 @@ { "source": { "scan": "./src/**", + "exclude": ["./src/dependencies/**"], "extensions": [".js", ".es6"], "alias": { "common": "shared/common", From 1817aa5f9c8a7a9df62e31f86414836cb662538e Mon Sep 17 00:00:00 2001 From: kuakman <3dimentionar@gmail.com> Date: Fri, 17 Mar 2017 23:57:14 -0700 Subject: [PATCH 29/39] cli: checkpoint - Change Implementation of util.visitor.Visitor#visit to not use proxies. Increases performance. The visited instance now is decorated when visited by visitor. No proxifying resolution on every property/method call. --- lib/bin/sqbox.es6 | 6 ++- lib/util/mixins.es6 | 31 +++----------- lib/util/visitor/visited.es6 | 45 --------------------- lib/util/visitor/visitor.es6 | 43 +++++++++++++++++++- lib/visitors/commander.es6 | 12 ++++++ lib/visitors/configuration.es6 | 13 +++++- test/lib/command.spec.es6 | 3 +- test/lib/util/visitor/visited.spec.es6 | 56 -------------------------- test/lib/util/visitor/visitor.spec.es6 | 56 +++++++++++++++++++++++++- 9 files changed, 131 insertions(+), 134 deletions(-) diff --git a/lib/bin/sqbox.es6 b/lib/bin/sqbox.es6 index cdef8a3..e4f607a 100644 --- a/lib/bin/sqbox.es6 +++ b/lib/bin/sqbox.es6 @@ -48,7 +48,9 @@ class SquareBox extends Command { * @return {bin.SquareBox} **/ before() { - return super.before().build(); + super.before(); + this.commander.build(); + return this; } /** @@ -59,7 +61,7 @@ class SquareBox extends Command { **/ run() { this.before(); - this.parse().then(_.bind(this.onConfiguration, this)); + this.configuration.parse().then(_.bind(this.onConfiguration, this)); return this; } diff --git a/lib/util/mixins.es6 b/lib/util/mixins.es6 index 9a0b577..d3c89cb 100644 --- a/lib/util/mixins.es6 +++ b/lib/util/mixins.es6 @@ -70,34 +70,15 @@ _.mixin({ }, /** - * Returns true if the content is in json format, false otherwise. + * Returns an array of all method names of a given object. * @public - * @param {String} content - content to be evaluated + * @param {String} o - object to be evaluated * @return {Boolean} **/ - isJson: function(content) { - try { - JSON.parse(content); - return true; - } catch(ex) { - return false; - } - }, - - /** - * Returns true if the content is in javascript format, false otherwise. - * @FIXME: Need to improve validation against javascript - * @public - * @param {String} content - content to be evaluated - * @return {Boolean} - **/ - isJS: function(content) { - try { - JSON.parse(content); - return false; - } catch(ex) { - return true; - } + methods: function(o) { + return _.filter(Object.getOwnPropertyNames(o), (name) => { + return (typeof(o[name]) === 'function'); + }); }, /** diff --git a/lib/util/visitor/visited.es6 b/lib/util/visitor/visited.es6 index 86dd6d6..ecd64ae 100644 --- a/lib/util/visitor/visited.es6 +++ b/lib/util/visitor/visited.es6 @@ -25,51 +25,6 @@ class Visited extends EventEmitter { return extend(true, this, ...args); } - /** - * Resolves proxified function binding with context - * @private - * @param {Any} target - proxy target - * @param {String} property - property name - * @param {Function} func - proxified function - * @return {Function} - **/ - _context(target, property, func) { - return _.defined(this[property]) ? this[property] : _.bind(func, target, this); - } - - /** - * Proxy getPrototypeOf trap override - * @public - * @param {Any} target - target reference - * @return {Object} - **/ - getPrototypeOf(target) { - return this.constructor.prototype; - } - - /** - * Proxy ownKeys trap override - * @public - * @param {Any} target - visited proxy target - * @return {Array} - **/ - ownKeys(target) { - return _.keys(this); - } - - /** - * Proxy get trap override - * @public - * @param {Any} target - visited target reference - * @param {String} property - visited target property - * @param {Any} receiver - visited target constructor - * @return {Any} - **/ - get(target, property, receiver) { - let value = _.defined(this[property]) ? this[property] : target[property]; - return _.isFunction(value) ? this._context(target, property, value) : value; - } - /** * Returns true if a given visitor is defined and an instance of util.visitor.Visitor, false otherwise * @public diff --git a/lib/util/visitor/visitor.es6 b/lib/util/visitor/visitor.es6 index 34e0454..36f4323 100644 --- a/lib/util/visitor/visitor.es6 +++ b/lib/util/visitor/visitor.es6 @@ -4,6 +4,7 @@ **/ import { EventEmitter } from 'events'; import _ from 'underscore'; +import _s from 'underscore.string'; import extend from 'extend'; import Visited from 'util/visitor/visited'; import InterfaceException from 'util/exception/proxy/interface'; @@ -25,6 +26,32 @@ class Visitor extends EventEmitter { return extend(true, this, ...args); } + /** + * Filter private methods declared on this visitor + * @public + * @return {Array} + **/ + _methods() { + const { prototype } = this.constructor; + return _.filter(_.methods(prototype).concat(_.methods(this)), (func) => { + return !_s.startsWith(func, '_') && !_.contains(Visitor._methods, func); + }); + } + + /** + * Decorates visited instance with methods declared on this visitor + * @private + * @param {util.visitor.Visited} vi - instance to be visited by this visitor + * @param {Any} [...args] - arguments passed to the visitor who visit the current visited instance + * @return {util.visitor.Visited} + **/ + _decorate(vi, ...args) { + return _.reduce(this._methods(), (memo, method) => { + if(!_.defined(memo[method])) memo[method] = _.bind(this[method], this, vi, ...args); + return memo; + }, vi); + } + /** * Returns true if the visited instance implements Visited interface. * Otherwise, this method will raise an InterfaceException. @@ -45,11 +72,11 @@ class Visitor extends EventEmitter { * This method is likely to be overriden by subsclasses of this visitor when needed. * @public * @param {util.visitor.Visited} vi - instance to be visited by this visitor - * @param {Any} [...args] - arguments passed to the vistior who visit the current visited instance + * @param {Any} [...args] - arguments passed to the visitor who visit the current visited instance * @return {util.visitor.Visited} **/ visit(vi, ...args) { - return this.validate(vi) ? new Proxy(this, vi) : null; + return this.validate(vi) ? this._decorate(vi, ...args) : null; } /** @@ -61,6 +88,18 @@ class Visitor extends EventEmitter { return 'Visitor'; } + /** + * Visitor Methods to filter out of the proxifing visit strategy + * @static + * @type {Array} + **/ + static _methods = [ + 'visit', + 'validate', + 'name', + 'constructor' + ].concat(_.functions(EventEmitter.prototype)); + /** * Static Constructor * @static diff --git a/lib/visitors/commander.es6 b/lib/visitors/commander.es6 index fad8fca..bbd075a 100644 --- a/lib/visitors/commander.es6 +++ b/lib/visitors/commander.es6 @@ -118,6 +118,18 @@ class Commander extends Visitor { return this.command; } + /** + * Visit Strategy + * @public + * @override + * @param {util.visitor.Visited} vi - instance to be visited by this visitor + * @param {Any} [...args] - arguments passed to the visitor + * @return {util.visitor.Visited} + **/ + visit(vi, ...args) { + return this.validate(vi) ? extend(false, vi, { commander: this }) : null; + } + /** * Parses Configuration options * @public diff --git a/lib/visitors/configuration.es6 b/lib/visitors/configuration.es6 index f765ee9..4f7f0c6 100644 --- a/lib/visitors/configuration.es6 +++ b/lib/visitors/configuration.es6 @@ -98,6 +98,18 @@ class Configuration extends Visitor { return this; } + /** + * Visit Strategy + * @public + * @override + * @param {util.visitor.Visited} vi - instance to be visited by this visitor + * @param {Any} [...args] - arguments passed to the visitor + * @return {util.visitor.Visited} + **/ + visit(vi, ...args) { + return this.validate(vi) ? extend(false, vi, { configuration: this }) : null; + } + /** * Parse Configuration based on source * @public @@ -131,7 +143,6 @@ class Configuration extends Visitor { ._target(result.target) ._logger(result.logLevel) ._override(this.command.options); - console.log(this.command); return this; } diff --git a/test/lib/command.spec.es6 b/test/lib/command.spec.es6 index 15d4710..1ecc207 100644 --- a/test/lib/command.spec.es6 +++ b/test/lib/command.spec.es6 @@ -34,7 +34,8 @@ describe('Command', function() { describe('#toJSON()', () => { it('Should return a json representation', () => { - const exp = Command.new({ env: 'production' }); + const exp = Command.new({ env: 'production' }).toJSON(); + assert.property(exp, 'env'); }); }); diff --git a/test/lib/util/visitor/visited.spec.es6 b/test/lib/util/visitor/visited.spec.es6 index 3ce4c1f..b7956e6 100644 --- a/test/lib/util/visitor/visited.spec.es6 +++ b/test/lib/util/visitor/visited.spec.es6 @@ -39,62 +39,6 @@ describe('util.visitor.Visited', function() { }); - describe('getPrototypeOf', () => { - - it('Should return visited prototype when instanceOf evaluation occur', () => { - const target = function() {}; - const exp = Visited.new({}); - assert.equal(exp.getPrototypeOf(target), exp.constructor.prototype); - }); - - }); - - describe('ownKeys()', () => { - - it('Should always return visited own keys on proxified target', () => { - const target = { name: 'Target' }; - const exp = Visited.new({ name: 'visited' }); - assert.lengthOf(_.keys(exp), exp.ownKeys(target).length); - }); - - }); - - describe('get()', () => { - - it('Should proxify input with Visited: access to target property', () => { - const input = { target: 'fromTarget', targetMethod: this.sandbox.spy() }; - const exp = Visited.new({ visited: 'fromVisited', visitedMethod: this.sandbox.spy() }); - - // Target Visitor - exp.get(input, 'targetMethod')(); - assert.equal('fromTarget', exp.get(input, 'target')); - assert.isTrue(input.targetMethod.calledOnce); - assert.equal(input.targetMethod.getCall(0).args[0], exp); - - // Visited - exp.get(input, 'visitedMethod')(); - assert.equal('fromVisited', exp.get(input, 'visited')); - assert.isTrue(exp.visitedMethod.calledOnce); - assert.lengthOf(exp.visitedMethod.getCall(0).args, 0); - - assert.isNotNull(exp.accept); - }); - - it('Should proxify the visited always if property/function names have the same name/key', () => { - const input = { property: 'fromTarget', method: this.sandbox.spy() }; - const exp = Visited.new({ property: 'fromVisited', method: this.sandbox.spy() }); - - // Always Visited - exp.get(input, 'method')(); - assert.equal('fromVisited', exp.get(input, 'property')); - assert.isTrue(exp.method.calledOnce); - assert.isFalse(input.method.called); - - assert.isNotNull(exp.accept); - }); - - }); - describe('validate()', () => { it('Should return true', () => { diff --git a/test/lib/util/visitor/visitor.spec.es6 b/test/lib/util/visitor/visitor.spec.es6 index 141b781..881b675 100644 --- a/test/lib/util/visitor/visitor.spec.es6 +++ b/test/lib/util/visitor/visitor.spec.es6 @@ -26,7 +26,7 @@ describe('util.visitor.Visitor', function() { delete this.sandbox; }); - describe('constructor', () => { + describe('constructor()', () => { it('Should get a new instance', () => { const exp = Visitor.new(); @@ -36,7 +36,7 @@ describe('util.visitor.Visitor', function() { }); - describe('validate', () => { + describe('validate()', () => { it('Should return true', () => { const exp = Visitor.new(); @@ -57,22 +57,74 @@ describe('util.visitor.Visitor', function() { }); + describe('_methods()', () => { + + it('Should filter visitor internal methods', () => { + const vi = Visitor.new({ someMethod: () => {} }); + const exp = vi._methods(); + assert.lengthOf(exp, 1); + assert.equal('someMethod', exp[0]); + }); + + }); + + describe('_decorate()', () => { + + it('Should decorate visited object', () => { + const arg = { prop: 'one' }; + const exp = Visitor.new({ func: this.sandbox.spy() }); + const input = Visited.new(); + + const spyWith = exp.func.withArgs(input, arg); + + const expMethods = this.mockProto.expects('_methods') + .once() + .returns(['func']); + + assert.instanceOf(exp._decorate(input), Visited); + + input.func(arg); + + assert.isTrue(spyWith.calledOnce); + assert.isTrue(spyWith.calledOn(exp)); + assert.isTrue(spyWith.calledWith(input, arg)); + }); + + it('Should skip methods already defined in visited', () => { + const exp = Visitor.new({ func: () => {}, existent: this.sandbox.spy() }); + const input = Visited.new({ existent: () => {} }); + + assert.instanceOf(exp._decorate(input), Visited); + input.existent(); + assert.isFalse(exp.existent.calledOnce); + }); + + }); + describe('visit', () => { it('Should visit the visted instance', () => { const exp = Visitor.new(); const input = Visited.new({}); + const expDecorate = this.mockProto.expects('_decorate') + .once() + .withArgs(input) + .returns(input); + const expValidate = this.mockProto.expects('validate') .once() .withArgs(input) .returns(true); assert.instanceOf(exp.visit(input), Object); + assert.isTrue(expValidate.calledBefore(expDecorate)); }); it('Should NOT visit the visted instance', () => { const exp = Visitor.new(); const input = {}; + const expDecorate = this.mockProto.expects('_decorate').never(); + const expValidate = this.mockProto.expects('validate') .once() .withArgs(input) From 44aa89c30bd61f7d66596aed2db579def1327ae0 Mon Sep 17 00:00:00 2001 From: kuakman <3dimentionar@gmail.com> Date: Sat, 18 Mar 2017 16:04:21 -0700 Subject: [PATCH 30/39] cli: checkpoint - Brokedown commander yargs methods into decorator helpers. Progress on adding unit test coverage on visitors.Commander Visitor. --- lib/bin/sqbox.es6 | 3 +- lib/visitors/commander.es6 | 168 ++++++----------- lib/visitors/commander/commands.es6 | 54 ++++++ lib/visitors/commander/epilogue.es6 | 16 ++ lib/visitors/commander/parse.es6 | 17 ++ lib/visitors/commander/usage.es6 | 24 +++ lib/visitors/commander/version.es6 | 23 +++ lib/visitors/configuration.es6 | 2 +- test/lib/bin/sqbox.spec.es6 | 4 +- test/lib/command.spec.es6 | 4 +- test/lib/util/adt/iterator.spec.es6 | 14 +- test/lib/util/adt/stack.spec.es6 | 10 +- test/lib/util/visitor/visited.spec.es6 | 2 +- test/lib/util/visitor/visitor.spec.es6 | 2 +- test/lib/visitors/commander.spec.es6 | 170 +++++++++++++++++- test/lib/visitors/commander/commands.spec.es6 | 0 test/lib/visitors/commander/epilogue.spec.es6 | 0 test/lib/visitors/commander/parse.spec.es6 | 0 test/lib/visitors/commander/usage.spec.es6 | 0 test/lib/visitors/commander/version.spec.es6 | 0 test/lib/visitors/configuration.spec.es6 | 0 .../configuration/formatter/alias.spec.es6 | 0 .../configuration/formatter/exclude.spec.es6 | 0 .../formatter/extensions.spec.es6 | 0 .../configuration/formatter/target.spec.es6 | 0 .../lib/visitors/configuration/local.spec.es6 | 0 .../visitors/configuration/remote.spec.es6 | 0 27 files changed, 383 insertions(+), 130 deletions(-) create mode 100644 lib/visitors/commander/commands.es6 create mode 100644 lib/visitors/commander/epilogue.es6 create mode 100644 lib/visitors/commander/parse.es6 create mode 100644 lib/visitors/commander/usage.es6 create mode 100644 lib/visitors/commander/version.es6 create mode 100644 test/lib/visitors/commander/commands.spec.es6 create mode 100644 test/lib/visitors/commander/epilogue.spec.es6 create mode 100644 test/lib/visitors/commander/parse.spec.es6 create mode 100644 test/lib/visitors/commander/usage.spec.es6 create mode 100644 test/lib/visitors/commander/version.spec.es6 create mode 100644 test/lib/visitors/configuration.spec.es6 create mode 100644 test/lib/visitors/configuration/formatter/alias.spec.es6 create mode 100644 test/lib/visitors/configuration/formatter/exclude.spec.es6 create mode 100644 test/lib/visitors/configuration/formatter/extensions.spec.es6 create mode 100644 test/lib/visitors/configuration/formatter/target.spec.es6 create mode 100644 test/lib/visitors/configuration/local.spec.es6 create mode 100644 test/lib/visitors/configuration/remote.spec.es6 diff --git a/lib/bin/sqbox.es6 b/lib/bin/sqbox.es6 index e4f607a..8624bf0 100644 --- a/lib/bin/sqbox.es6 +++ b/lib/bin/sqbox.es6 @@ -49,7 +49,7 @@ class SquareBox extends Command { **/ before() { super.before(); - this.commander.build(); + this.commander.read(); return this; } @@ -73,6 +73,7 @@ class SquareBox extends Command { **/ onConfiguration() { const { path } = this.options; + //console.log(_.pick(this, Command.options)); // TODO: Factory.get(path, this.params()).run(); return this; } diff --git a/lib/visitors/commander.es6 b/lib/visitors/commander.es6 index bbd075a..ea8debb 100644 --- a/lib/visitors/commander.es6 +++ b/lib/visitors/commander.es6 @@ -28,96 +28,6 @@ class Commander extends Visitor { return super({ command }); } - /** - * Default Strategy to add version option to the CLI - * @public - * @return {visitors.Commander} - **/ - _version() { - try { - yargs.version(require(resolve(this.command.dirname, '..', 'package.json')).version); - } catch(ex) { - logger(`[WARN] SquareBox Version not detected caused by ${ex.message}`).debug(logger.yellow); - } - return this; - } - - /** - * Default Strategy to add usage to the CLI - * @public - * @param {String} [message = 'sqbox [args]'] - usage message to display - * @return {visitors.Commander} - **/ - _usage(message = 'sqbox [args]') { - yargs.usage(chalk.white(message)); - return this; - } - - /** - * Default Strategy to add commands into the CLI via yargs - * @private - * @return {visitors.Commander} - **/ - _commands() { - this.command.constructor.commands.reduce(_.bind(this._new, this), yargs); - return this; - } - - /** - * Reducer appends a command into the memoized yargs reference - * @public - * @param {yargs} memo - memoized yargs reference - * @param {Command} command - command instance - * @return {yargs} - **/ - _new(memo, command) { - return memo.command(_.reduce(Commander.builder(), _.bind(this._create, this, command), {})); - } - - /** - * Build a new command object and - * @public - * @param {Command} command - command instance - * @param {Object} memo - memoized object - * @param {String} option - Yargs command option - * @return {Object} - **/ - _create(command, memo, option) { - memo[option] = this[`_${option}`](command); - return memo; - } - - /** - * Default Strategy that adds an epilogue information - * @private - * @return {visitors.Commander} - **/ - _epilogue() { - yargs.epilogue(chalk.cyan(`For more information, please visit http://squarebox.nahuel.io/`)); - return this; - } - - /** - * Default Strategy that controls yargs width used for displaying information - * @private - * @return {visitors.Commander} - **/ - _width() { - yargs.wrap(120); - return this; - } - - /** - * Default Strategy for yargs argument parsing and returns the reference to the visited - * @public - * @return {util.visitor.Visited} - **/ - _parse() { - yargs.parse(this.programArgv(), (err, argv, output) => - this.command.emit(Commander.events.parse, err, argv, output)); - return this.command; - } - /** * Visit Strategy * @public @@ -127,27 +37,39 @@ class Commander extends Visitor { * @return {util.visitor.Visited} **/ visit(vi, ...args) { - return this.validate(vi) ? extend(false, vi, { commander: this }) : null; + return this.validate(vi) ? extend(false, vi, { commander: this }) : vi; } /** - * Parses Configuration options + * Reads CLI arguments and build yargs interface * @public * @return {yargs} **/ - build() { - yargs.reset(); - return this._version()._usage()._commands()._epilogue()._width()._parse(); + read() { + return _.reduce(Commander.decorators, (memo, decorator) => { + return Factory.get(decorator, memo, this.command, this); + }, yargs.reset().wrap(120)); + } + + /** + * Commander CLI Options Parse Handler + * @public + * @param {Error} [err] - Parse Error + * @param {Object} argv - parsed options + * @param {Object} output - yargs output + * @return {visitors.Commander} + **/ + onParse(err, argv, output) { + return this.emit(Commander.events.parse, err, argv, output); } /** - * Default Strategy to retrieve program arguments + * Commander Arguments * @private - * @param {util.visitor.Visited} ctx - context reference - * @param {Any} [args = process.argv] - optional arguments + * @param {Array} [args = process.argv] - command line arguments verbatim * @return {Array} **/ - programArgv(ctx, args = process.argv) { + _args(args = process.argv) { return args; } @@ -199,10 +121,20 @@ class Commander extends Visitor { * @return {Function} **/ _handler(command) { - return (argv) => { - Factory.register(command.path); - this.command.settings({ options: extend(false, _.omit(argv, Commander.ignore), { path: command.path }) }); - }; + return (argv) => { return this._onHandler(command, argv); }; + } + + /** + * Default Command Handler + * @private + * @param {Command} command - command instance + * @param {Object} argv - yargs parsed arguments + * @return {viitors.Commander} + **/ + _onHandler(command, argv) { + Factory.register(command.path); + this.command.settings({ options: extend(false, _.omit(argv, Commander.ignore), { path: command.path }) }); + return this; } /** @@ -214,6 +146,19 @@ class Commander extends Visitor { return 'CommanderVisitor'; } + /** + * Commander decorators + * @static + * @type {Array} + **/ + static decorators = [ + 'visitors/commander/version', + 'visitors/commander/usage', + 'visitors/commander/epilogue', + 'visitors/commander/commands', + 'visitors/commander/parse' + ]; + /** * Commander Events * @static @@ -221,9 +166,9 @@ class Commander extends Visitor { **/ static events = { /** - * @event read + * @event parse **/ - parse: 'visitors:commander:read' + parse: 'visitors:commander:parse' } /** @@ -231,15 +176,18 @@ class Commander extends Visitor { * @static * @type {Array} **/ - static ignore = ['$0', '_', 'version'] + static ignore = ['$0', '_', 'version']; /** - * Yargs Command Builder option names + * Static Constructor * @static - * @return {Array} + * @override + * @param {Any} [...args] - constructor arguments + * @return {visitors.Commander} **/ - static builder() { - return ['command', 'aliases', 'desc', 'builder', 'handler']; + static new(...args) { + Factory.registerAll(Commander.decorators); + return new this(...args); } } diff --git a/lib/visitors/commander/commands.es6 b/lib/visitors/commander/commands.es6 new file mode 100644 index 0000000..2cd1f4c --- /dev/null +++ b/lib/visitors/commander/commands.es6 @@ -0,0 +1,54 @@ +/** +* @module visitors.commander +* @author Patricio Ferreira <3dimentionar@gmail.com> +**/ +import _ from 'underscore'; + +/** +* Yargs Command Builder Option Names +* @const +* @private +* @type {Array} +**/ +const Builder = ['command', 'aliases', 'desc', 'builder', 'handler']; + +/** +* Creates command's handler +* @const +* @private +* @param {visitors.Commander} ctx - commander visitor reference +* @param {Object} command - command options +* @param {Object} memo - memoized object +* @param {String} option - Yargs command option +* @return {Object} +**/ +const create = (ctx, command, memo, option) => { + memo[option] = ctx[`_${option}`](command); + return memo; +}; + +/** +* Create Single Yarg Command +* @const +* @private +* @param {visitors.Commander} ctx - commander visitor reference +* @param {yargs} memo - memoized yargs reference +* @param {Object} current - current command +* @return {yargs} +**/ +const command = (ctx, memo, current) => { + return memo.command(_.reduce(Builder, _.bind(create, this, ctx, current), {})); +}; + +/** +* Create Yargs Commands +* @static +* @param {yargs} yargs - yargs reference +* @param {Command} c - current command +* @param {visitors.Commander} ctx - commander visitor reference +* @return {yargs} +**/ +export default (yargs, c, ctx) => { + const { commands } = c.constructor; + return commands.reduce(_.bind(command, this, ctx), yargs); +}; diff --git a/lib/visitors/commander/epilogue.es6 b/lib/visitors/commander/epilogue.es6 new file mode 100644 index 0000000..5e16c20 --- /dev/null +++ b/lib/visitors/commander/epilogue.es6 @@ -0,0 +1,16 @@ +/** +* @module visitors.commander +* @author Patricio Ferreira <3dimentionar@gmail.com> +**/ +import _ from 'underscore'; +import chalk from 'chalk'; + +/** +* Yargs Epilogue +* @static +* @param {yargs} yargs - yargs reference +* @return {yargs} +**/ +export default (yargs) => { + return yargs.epilogue(chalk.cyan(`For more information, please visit http://squarebox.nahuel.io/`)); +}; diff --git a/lib/visitors/commander/parse.es6 b/lib/visitors/commander/parse.es6 new file mode 100644 index 0000000..517caed --- /dev/null +++ b/lib/visitors/commander/parse.es6 @@ -0,0 +1,17 @@ +/** +* @module visitors.commander +* @author Patricio Ferreira <3dimentionar@gmail.com> +**/ +import _ from 'underscore'; + +/** +* Yargs Parse +* @static +* @param {yargs} yargs - yargs reference +* @param {Command} c - current command +* @param {visitors.Commander} ctx - commander visitor reference +* @return {util.visitor.Visited} +**/ +export default (yargs, command, ctx) => { + return yargs.parse(ctx._args(), _.bind(ctx.onParse, ctx)); +}; diff --git a/lib/visitors/commander/usage.es6 b/lib/visitors/commander/usage.es6 new file mode 100644 index 0000000..6e27312 --- /dev/null +++ b/lib/visitors/commander/usage.es6 @@ -0,0 +1,24 @@ +/** +* @module visitors.commander +* @author Patricio Ferreira <3dimentionar@gmail.com> +**/ +import _ from 'underscore'; +import chalk from 'chalk'; + +/** +* Usage Message +* @const +* @private +* @type {String} +**/ +const message = 'sqbox [args]'; + +/** +* Yargs Usage +* @static +* @param {yargs} yargs - yargs reference +* @return {yargs} +**/ +export default (yargs) => { + return yargs.usage(chalk.white(message)); +}; diff --git a/lib/visitors/commander/version.es6 b/lib/visitors/commander/version.es6 new file mode 100644 index 0000000..990bb34 --- /dev/null +++ b/lib/visitors/commander/version.es6 @@ -0,0 +1,23 @@ +/** +* @module visitors.commander +* @author Patricio Ferreira <3dimentionar@gmail.com> +**/ +import { resolve } from 'path'; +import _ from 'underscore'; +import logger from 'util/logger/logger'; + +/** +* Yargs Version +* @static +* @param {yargs} yargs - yargs reference +* @param {Command} command - current command reference +* @return {yargs} +**/ +export default (yargs, command) => { + try { + return yargs.version(require(resolve(command.dirname, '..', 'package.json')).version); + } catch(ex) { + logger(`[WARN] SquareBox Version not detected caused by ${ex.message}`).debug(logger.yellow); + } + return yargs; +}; diff --git a/lib/visitors/configuration.es6 b/lib/visitors/configuration.es6 index 4f7f0c6..c9a7997 100644 --- a/lib/visitors/configuration.es6 +++ b/lib/visitors/configuration.es6 @@ -57,7 +57,7 @@ class Configuration extends Visitor { * @param {Object} memo - options being memoized * @param {Any} memo - options being memoized * @param {String} memo - options being memoized - * @return + * @return {Object} **/ _format(memo, value, key) { let ph = this.formatterPath(key); diff --git a/test/lib/bin/sqbox.spec.es6 b/test/lib/bin/sqbox.spec.es6 index 4234c58..9b16533 100644 --- a/test/lib/bin/sqbox.spec.es6 +++ b/test/lib/bin/sqbox.spec.es6 @@ -24,6 +24,7 @@ describe('bin.SquareBox', function() { this.sandbox.restore(); + delete this.mockCommander; delete this.mockProto; delete this.input; }); @@ -55,7 +56,8 @@ describe('bin.SquareBox', function() { '--a', 'common:./path/common', '--t', 'add>umd:./dist/umd,other>cjs:./dist/cjs' ]); - const expProgramArgv = this.mockCommander.expects('programArgv') + + this.mockCommander.expects('_args') .once() .returns(this.input); diff --git a/test/lib/command.spec.es6 b/test/lib/command.spec.es6 index 1ecc207..c94b1bb 100644 --- a/test/lib/command.spec.es6 +++ b/test/lib/command.spec.es6 @@ -23,7 +23,7 @@ describe('Command', function() { delete this.sandbox; }); - describe('#constructor', () => { + describe('constructor()', () => { it('Should get a new instance', () => { assert.instanceOf(Command.new(), Command); @@ -31,7 +31,7 @@ describe('Command', function() { }); - describe('#toJSON()', () => { + describe('toJSON()', () => { it('Should return a json representation', () => { const exp = Command.new({ env: 'production' }).toJSON(); diff --git a/test/lib/util/adt/iterator.spec.es6 b/test/lib/util/adt/iterator.spec.es6 index 42a1a91..9cfc77d 100644 --- a/test/lib/util/adt/iterator.spec.es6 +++ b/test/lib/util/adt/iterator.spec.es6 @@ -23,7 +23,7 @@ describe('util.adt.Iterator', function() { delete this.sandbox; }); - describe('#constructor', () => { + describe('constructor()', () => { it('Should get a new instance', () => { assert.instanceOf(Iterator.new(), Iterator); @@ -38,7 +38,7 @@ describe('util.adt.Iterator', function() { }); - describe('#_valid()', () => { + describe('_valid()', () => { it('Should return true (valid element)', () => { const exp = Iterator.new(); @@ -53,7 +53,7 @@ describe('util.adt.Iterator', function() { }); - describe('#set()', () => { + describe('set()', () => { it('Should set the iterator', () => { const exp = Iterator.new(); @@ -78,7 +78,7 @@ describe('util.adt.Iterator', function() { }); - describe('#hasNext()', () => { + describe('hasNext()', () => { it('Should return true and false', () => { const exp = Iterator.new([1,2]); @@ -91,7 +91,7 @@ describe('util.adt.Iterator', function() { }); - describe('#next()', () => { + describe('next()', () => { it('Should return next element', () => { const exp = Iterator.new([1,2]); @@ -102,7 +102,7 @@ describe('util.adt.Iterator', function() { }); - describe('#rewind()', () => { + describe('rewind()', () => { it('Should rewind the iterator (pointer to the begin of the iterator)', () => { const exp = Iterator.new([1,2]); @@ -117,7 +117,7 @@ describe('util.adt.Iterator', function() { }); - describe('#remove()', () => { + describe('remove()', () => { it('Should remove the current element by the pointer', () => { const exp = Iterator.new([1,2,3]); diff --git a/test/lib/util/adt/stack.spec.es6 b/test/lib/util/adt/stack.spec.es6 index cb80b0b..aaaebae 100644 --- a/test/lib/util/adt/stack.spec.es6 +++ b/test/lib/util/adt/stack.spec.es6 @@ -25,7 +25,7 @@ describe('util.adt.Stack', function() { delete this.sandbox; }); - describe('#constructor()', () => { + describe('constructor()', () => { it('Should get an instance (initial emtpy elements)', () => { const exp = Stack.new(); @@ -41,7 +41,7 @@ describe('util.adt.Stack', function() { }); - describe('#push()', () => { + describe('push()', () => { it('Should push a new element', () => { const toPush = { option: true }; @@ -65,7 +65,7 @@ describe('util.adt.Stack', function() { }); - describe('#peek()', () => { + describe('peek()', () => { it('Should get the first element', () => { const exp = Stack.new([1,2,3]); @@ -80,7 +80,7 @@ describe('util.adt.Stack', function() { }); - describe('#pop()', () => { + describe('pop()', () => { it('Should remove and get the first element', () => { const exp = Stack.new([{ env: 'dev' }, { env: 'stage' }], { interface: Command }); @@ -103,7 +103,7 @@ describe('util.adt.Stack', function() { }); - describe('#search()', () => { + describe('search()', () => { it('Should search and retrieve the position of an element', () => { const exp = Stack.new(['a','b','c']); diff --git a/test/lib/util/visitor/visited.spec.es6 b/test/lib/util/visitor/visited.spec.es6 index b7956e6..01bb71f 100644 --- a/test/lib/util/visitor/visited.spec.es6 +++ b/test/lib/util/visitor/visited.spec.es6 @@ -29,7 +29,7 @@ describe('util.visitor.Visited', function() { delete this.sandbox; }); - describe('constructor', () => { + describe('constructor()', () => { it('Should get a new instance', () => { const exp = Visited.new({ property: 'Visited' }); diff --git a/test/lib/util/visitor/visitor.spec.es6 b/test/lib/util/visitor/visitor.spec.es6 index 881b675..a10d885 100644 --- a/test/lib/util/visitor/visitor.spec.es6 +++ b/test/lib/util/visitor/visitor.spec.es6 @@ -101,7 +101,7 @@ describe('util.visitor.Visitor', function() { }); - describe('visit', () => { + describe('visit()', () => { it('Should visit the visted instance', () => { const exp = Visitor.new(); diff --git a/test/lib/visitors/commander.spec.es6 b/test/lib/visitors/commander.spec.es6 index 3e55151..30846e0 100644 --- a/test/lib/visitors/commander.spec.es6 +++ b/test/lib/visitors/commander.spec.es6 @@ -1 +1,169 @@ -// TODO: Mock process.argv to trick yargs!!! +/** +* @module visitors +* @author Patricio Ferreira <3dimentionar@gmail.com> +**/ +import Commander from 'visitors/commander'; +import Command from 'command'; +import chalk from 'chalk'; + +describe('visitors.Commander', function() { + + before(() => { + this.command = Command.new(); + if(!this.commander) this.commander = Commander.new(this.command); + this.sandbox = sinon.sandbox.create(); + }); + + beforeEach(() => { + this.mockProto = this.sandbox.mock(Commander.prototype); + }); + + afterEach(() => { + this.mockProto.verify(); + + this.sandbox.restore(); + + delete this.mockProto; + }); + + after(() => { + delete this.command; + delete this.commander; + delete this.sandbox; + }); + + describe('constructor()', () => { + + it('Should get an instance', () => { + assert.instanceOf(this.commander, Commander); + assert.instanceOf(this.commander.command, Command); + assert.equal('CommanderVisitor', this.commander.name); + }); + + }); + + describe('_args()', () => { + + it('Should return default arguments', () => { + const res = this.commander._args(); + assert.isArray(res); + }); + + it('Should return custom arguments', () => { + const res = this.commander._args(['param', '--value']); + assert.isArray(res); + assert.lengthOf(res, 2); + }); + + }); + + describe('_command()', () => { + + it('Should return string representation of a command for yargs (with abbr)', () => { + const input = { name: 'bundle', abbr: 'be' }; + const exp = chalk.green(`${input.name}, -${input.abbr}`); + const res = this.commander._command(input); + assert.equal(exp, res); + }); + + it('Should return string representation of a command for yargs (without abbr)', () => { + const input = { name: 'bundle' }; + const exp = chalk.green(`${input.name}`); + const res = this.commander._command(input); + assert.equal(exp, res); + }); + + }); + + describe('_aliases()', () => { + + it('Should return aliases', () => { + const input = { aliases: ['b'] }; + assert.equal(this.commander._aliases(input), input.aliases); + }); + + }); + + describe('_desc()', () => { + + it('Should return command description', () => { + const input = { description: 'my description' }; + assert.equal(this.commander._desc(input), chalk.yellow(input.description)); + }); + + }); + + describe('_builder()', () => { + + it('Should return options (defaults)', () => { + const input = { options: { url: { default: 'http://squarebox.nahuel.io/' } } }; + assert.deepEqual(this.commander._builder(input), input.options); + }); + + }); + + describe('_handler()', () => { + + it('Should return a function handler for the command', () => { + const input = { option: 1, option: 2 }; + const command = {}; + const expHandler = this.mockProto.expects('_onHandler') + .once() + .withArgs(command, input) + .returns(this.commander); + + const exp = this.commander._handler(command, {}); + assert.isFunction(exp); + exp(input); + }); + + }); + + describe('_onHandler()', () => { + + xit('Should execute command handler for yargs', () => { + + }); + + }); + + describe('visit()', () => { + + it('Should visit commander visitor', () => { + const expValidate = this.mockProto.expects('validate') + .once() + .withArgs(this.command) + .returns(true); + + const exp = this.commander.visit(this.command); + assert.instanceOf(exp, Command); + assert.property(exp, 'commander'); + }); + + it('Should NOT visit commander visitor (invalid)', () => { + const input = Command.new(); + const expValidate = this.mockProto.expects('validate') + .once() + .withArgs(input) + .returns(false); + + const exp = this.commander.visit(input); + assert.instanceOf(exp, Command); + assert.notProperty(exp, 'commander'); + }); + + }); + + describe('onParse()', () => { + + xit('Should execute onParse handler', () => {}); + + }); + + describe('read()', () => { + + xit('Should read CLI arguments via this visitor', () => {}); + + }); + +}); diff --git a/test/lib/visitors/commander/commands.spec.es6 b/test/lib/visitors/commander/commands.spec.es6 new file mode 100644 index 0000000..e69de29 diff --git a/test/lib/visitors/commander/epilogue.spec.es6 b/test/lib/visitors/commander/epilogue.spec.es6 new file mode 100644 index 0000000..e69de29 diff --git a/test/lib/visitors/commander/parse.spec.es6 b/test/lib/visitors/commander/parse.spec.es6 new file mode 100644 index 0000000..e69de29 diff --git a/test/lib/visitors/commander/usage.spec.es6 b/test/lib/visitors/commander/usage.spec.es6 new file mode 100644 index 0000000..e69de29 diff --git a/test/lib/visitors/commander/version.spec.es6 b/test/lib/visitors/commander/version.spec.es6 new file mode 100644 index 0000000..e69de29 diff --git a/test/lib/visitors/configuration.spec.es6 b/test/lib/visitors/configuration.spec.es6 new file mode 100644 index 0000000..e69de29 diff --git a/test/lib/visitors/configuration/formatter/alias.spec.es6 b/test/lib/visitors/configuration/formatter/alias.spec.es6 new file mode 100644 index 0000000..e69de29 diff --git a/test/lib/visitors/configuration/formatter/exclude.spec.es6 b/test/lib/visitors/configuration/formatter/exclude.spec.es6 new file mode 100644 index 0000000..e69de29 diff --git a/test/lib/visitors/configuration/formatter/extensions.spec.es6 b/test/lib/visitors/configuration/formatter/extensions.spec.es6 new file mode 100644 index 0000000..e69de29 diff --git a/test/lib/visitors/configuration/formatter/target.spec.es6 b/test/lib/visitors/configuration/formatter/target.spec.es6 new file mode 100644 index 0000000..e69de29 diff --git a/test/lib/visitors/configuration/local.spec.es6 b/test/lib/visitors/configuration/local.spec.es6 new file mode 100644 index 0000000..e69de29 diff --git a/test/lib/visitors/configuration/remote.spec.es6 b/test/lib/visitors/configuration/remote.spec.es6 new file mode 100644 index 0000000..e69de29 From 9be9603a666330bf6b238eefb5c0ec7a0be418fa Mon Sep 17 00:00:00 2001 From: kuakman <3dimentionar@gmail.com> Date: Mon, 20 Mar 2017 08:58:43 -0700 Subject: [PATCH 31/39] cli: checkpoint - Added unit test coverage to visitors.Commander. --- lib/visitors/commander.es6 | 2 +- package.json | 1 + test/lib/visitors/commander.spec.es6 | 54 ++++++++++++++++++++++++++-- 3 files changed, 53 insertions(+), 4 deletions(-) diff --git a/lib/visitors/commander.es6 b/lib/visitors/commander.es6 index ea8debb..bff0ee6 100644 --- a/lib/visitors/commander.es6 +++ b/lib/visitors/commander.es6 @@ -129,7 +129,7 @@ class Commander extends Visitor { * @private * @param {Command} command - command instance * @param {Object} argv - yargs parsed arguments - * @return {viitors.Commander} + * @return {visitors.Commander} **/ _onHandler(command, argv) { Factory.register(command.path); diff --git a/package.json b/package.json index 9066c8f..c728c03 100644 --- a/package.json +++ b/package.json @@ -55,6 +55,7 @@ "babel-preset-stage-2": "6.22.0", "babel-register": "6.23.0", "chalk": "1.1.3", + "d3": "4.7.3", "extend": "3.0.0", "fs-extra": "2.0.0", "glob": "7.1.1", diff --git a/test/lib/visitors/commander.spec.es6 b/test/lib/visitors/commander.spec.es6 index 30846e0..84538c9 100644 --- a/test/lib/visitors/commander.spec.es6 +++ b/test/lib/visitors/commander.spec.es6 @@ -3,6 +3,8 @@ * @author Patricio Ferreira <3dimentionar@gmail.com> **/ import Commander from 'visitors/commander'; +import Factory from 'util/factory/factory'; +import yargs from 'yargs'; import Command from 'command'; import chalk from 'chalk'; @@ -16,14 +18,23 @@ describe('visitors.Commander', function() { beforeEach(() => { this.mockProto = this.sandbox.mock(Commander.prototype); + this.mockYargs = this.sandbox.mock(yargs); + this.mockCommand = this.sandbox.mock(Command.prototype); + this.mockFactory = this.sandbox.mock(Factory); }); afterEach(() => { this.mockProto.verify(); + this.mockYargs.verify(); + this.mockFactory.verify(); + this.mockCommand.verify(); this.sandbox.restore(); delete this.mockProto; + delete this.mockYargs; + delete this.mockFactory; + delete this.mockCommand; }); after(() => { @@ -121,8 +132,24 @@ describe('visitors.Commander', function() { describe('_onHandler()', () => { - xit('Should execute command handler for yargs', () => { + it('Should execute command handler for yargs', () => { + const command = { path: 'bundle/bundle' }; + const inputArgv = { $0: '/path/to/script.js', url: 'http://squarebox.nahuel.io' }; + const expCommand = { options: { path: command.path, url: inputArgv.url } }; + const exp = Commander.new(this.command); + const expRegister = this.mockFactory.expects('register') + .once() + .withArgs(command.path) + .returns(Factory); + + const expSettings = this.mockCommand.expects('settings') + .once() + .withArgs(expCommand) + .returns(this.command); + + assert.instanceOf(this.commander._onHandler(command, inputArgv), Commander); + assert.isTrue(expRegister.calledBefore(expSettings)); }); }); @@ -156,13 +183,34 @@ describe('visitors.Commander', function() { describe('onParse()', () => { - xit('Should execute onParse handler', () => {}); + it('Should execute onParse handler', () => { + const err = null; + const inputArgv = { $0: '/path/to/script.js', url: 'http://squarebox.nahuel.io' }; + const output = {}; + + const expEmit = this.mockProto.expects('emit') + .once() + .withArgs(Commander.events.parse, err, inputArgv, output) + .returns(true); + + assert.isTrue(this.commander.onParse(err, inputArgv, output)); + }); }); describe('read()', () => { - xit('Should read CLI arguments via this visitor', () => {}); + it('Should read CLI arguments via this visitor', () => { + const expReset = this.mockYargs.expects('reset').once().returns(yargs); + const expWrap = this.mockYargs.expects('wrap').once().withArgs(120).returns(yargs); + + const expFactoryGet = this.mockFactory.expects('get') + .exactly(5) + .withArgs(sinon.match((v) => _.contains(Commander.decorators, v)), yargs, this.command, this.commander) + .returns(yargs); + + assert.typeOf(this.commander.read(), 'Function'); + }); }); From 9a4475afbe1763c4c8117b03a45e9f5e7c2a0a9b Mon Sep 17 00:00:00 2001 From: kuakman <3dimentionar@gmail.com> Date: Mon, 20 Mar 2017 10:16:03 -0700 Subject: [PATCH 32/39] cli: checkpoint - Progress on adding unit test coverage for visitors.Configuration. --- lib/visitors/configuration.es6 | 3 +- test/lib/visitors/configuration.spec.es6 | 164 +++++++++++++++++++++++ 2 files changed, 166 insertions(+), 1 deletion(-) diff --git a/lib/visitors/configuration.es6 b/lib/visitors/configuration.es6 index c9a7997..09b64c5 100644 --- a/lib/visitors/configuration.es6 +++ b/lib/visitors/configuration.es6 @@ -36,6 +36,7 @@ class Configuration extends Visitor { **/ _create(options, method) { this.queue.offer(Factory.get(method, this.command, options)); + return this; } /** @@ -107,7 +108,7 @@ class Configuration extends Visitor { * @return {util.visitor.Visited} **/ visit(vi, ...args) { - return this.validate(vi) ? extend(false, vi, { configuration: this }) : null; + return this.validate(vi) ? extend(false, vi, { configuration: this }) : vi; } /** diff --git a/test/lib/visitors/configuration.spec.es6 b/test/lib/visitors/configuration.spec.es6 index e69de29..bd45eb6 100644 --- a/test/lib/visitors/configuration.spec.es6 +++ b/test/lib/visitors/configuration.spec.es6 @@ -0,0 +1,164 @@ +/** +* @module visitors +* @author Patricio Ferreira <3dimentionar@gmail.com> +**/ +import Configuration from 'visitors/configuration'; +import Command from 'command'; +import Factory from 'util/factory/factory'; +import QueueAsync from 'util/adt/queue-async'; + +describe('visitors.Configuration', function() { + + before(() => { + this.command = Command.new(); + if(!this.configuration) this.configuration = Configuration.new(this.command); + this.sandbox = sinon.sandbox.create(); + }); + + beforeEach(() => { + this.mockProto = this.sandbox.mock(Configuration.prototype); + this.mockQueueAsync = this.sandbox.mock(QueueAsync.prototype); + this.mockCommand = this.sandbox.mock(Command.prototype); + this.mockFactory = this.sandbox.mock(Factory); + }); + + afterEach(() => { + this.mockProto.verify(); + this.mockQueueAsync.verify(); + this.mockFactory.verify(); + this.mockCommand.verify(); + + this.sandbox.restore(); + + delete this.mockProto; + delete this.mockQueueAsync; + delete this.mockFactory; + delete this.mockCommand; + }); + + after(() => { + delete this.command; + delete this.configuration; + delete this.sandbox; + }); + + describe('constructor()', () => { + + it('Should get a new instance', () => { + assert.instanceOf(this.configuration, Configuration); + assert.equal('ConfigurationVisitor', this.configuration.name); + assert.property(this.configuration, 'queue'); + assert.instanceOf(this.configuration.queue, QueueAsync); + }); + + }); + + describe('visit()', () => { + + it('Should visit configuration visitor', () => { + const expValidate = this.mockProto.expects('validate') + .once() + .withArgs(this.command) + .returns(true); + + const exp = this.configuration.visit(this.command); + assert.instanceOf(exp, Command); + assert.property(exp, 'configuration'); + }); + + it('Should NOT visit configuration visitor (invalid)', () => { + const input = Command.new(); + const expValidate = this.mockProto.expects('validate') + .once() + .withArgs(input) + .returns(false); + + const exp = this.configuration.visit(input); + assert.instanceOf(exp, Command); + assert.notProperty(exp, 'configuration'); + }); + + }); + + describe('_create()', () => { + + it('Should enqueue configuration decorator methods', () => { + const options = { option: true }; + const expGet = this.mockFactory.expects('get') + .once() + .withArgs('visitors/configuration/remote', this.command, options) + .returns(options); + + const expOffer = this.mockQueueAsync.expects('offer') + .once() + .withArgs(options) + .returns(this.configuration.queue); + + const exp = this.configuration._create(options, 'visitors/configuration/remote'); + assert.instanceOf(exp, Configuration); + }); + + }); + + describe('_format()', () => { + + xit('Should apply formatter to the current configuration option', () => {}); + xit('Should NOT apply formatter to the current configuration option (Factory doesn\'t exists)', () => {}); + + }); + + describe('_source()', () => { + + xit('Should apply source configuration options to the current command', () => {}); + + }); + + describe('_target()', () => { + + xit('Should apply target configuration options to the current command', () => {}); + + }); + + describe('_logger()', () => { + + xit('Should apply logger configuration option to the singleton logger', () => {}); + + }); + + describe('_override()', () => { + + xit('Should override configuration options with cli options', () => {}); + + }); + + describe('formatterPath()', () => { + + xit('Should resolve formatter path to the factory', () => {}); + + }); + + describe('onParse()', () => { + + xit('Should iterate over results gatter from commander to perform transformations', () => {}); + + }); + + describe('onParseError()', () => { + + xit('Should output possible warning', () => {}); + + }); + + describe('onOptions()', () => { + + xit('Should perform options transformations', () => {}); + + }); + + describe('parse()', () => { + + xit('Should parse commander options and apply configuration based on input', () => {}); + + }); + +}); From 2a88925ccb331128531920fcef23412d414b8646 Mon Sep 17 00:00:00 2001 From: kuakman <3dimentionar@gmail.com> Date: Tue, 21 Mar 2017 12:17:52 -0700 Subject: [PATCH 33/39] cli: checkpoint - Added unit test coverage to visitors.Configuration. Added sinon-stub-promise dev dependency into package.json. --- lib/util/mixins.es6 | 2 +- lib/visitors/configuration.es6 | 11 +- lib/visitors/formatter/json.es6 | 6 +- package.json | 3 +- test/global.js | 1 + test/lib/visitors/configuration.spec.es6 | 217 +++++++++++++++++++++-- 6 files changed, 220 insertions(+), 20 deletions(-) diff --git a/lib/util/mixins.es6 b/lib/util/mixins.es6 index d3c89cb..54864a0 100644 --- a/lib/util/mixins.es6 +++ b/lib/util/mixins.es6 @@ -54,7 +54,7 @@ _.mixin({ **/ isRealObject: function(o) { if(!_.defined(o) || !_.defined(o.constructor)) return false; - return ((o).constructor.toString().indexOf('Object') !== -1); + return ((o).constructor.toString().indexOf('function Object() {') !== -1); }, /** diff --git a/lib/visitors/configuration.es6 b/lib/visitors/configuration.es6 index 09b64c5..3215fce 100644 --- a/lib/visitors/configuration.es6 +++ b/lib/visitors/configuration.es6 @@ -84,7 +84,12 @@ class Configuration extends Visitor { * @return {visitors.Configuration} **/ _target(opts) { - extend(true, this.command, _.pick(opts, Configuration.cliOptions)); + extend(true, this.command, { + target: _.reduce(opts, (memo, cfg, name) => { + memo[name] = _.pick(cfg, Configuration.cliOptions); + return memo; + }, opts) + }); return this; } @@ -118,7 +123,7 @@ class Configuration extends Visitor { **/ parse() { Configuration.methods.forEach(_.bind(this._create, this, this.command.options)); - return this.queue.poll().then(_.bind(this.onParse, this)).catch(_.bind(this.onParseError, this)); + return this.queue.poll().then(_.bind(this.onParse, this), _.bind(this.onParseError, this)); } /** @@ -204,7 +209,7 @@ class Configuration extends Visitor { * @static * @type {Array} **/ - static cliOptions = ['scan', 'exclude', 'extensions', 'alias', 'target']; + static cliOptions = ['scan', 'exclude', 'extensions', 'alias', 'target', 'destination', 'format']; /** * Configuration Events diff --git a/lib/visitors/formatter/json.es6 b/lib/visitors/formatter/json.es6 index d4505ed..b6b7a90 100644 --- a/lib/visitors/formatter/json.es6 +++ b/lib/visitors/formatter/json.es6 @@ -31,8 +31,8 @@ class Json extends Visitor { * @param {String} k - current object's key * @return {Object} **/ - _reduce(m, v, k) { - if(_.isAdt(v)) this._clean(v, v); + _filterObject(m, v, k) { + if(_.isAdt(v)) return this._clean(v, v); if(!_.isFunction(v)) { m[k] = v; return m; } if(_.isArray(m)) m.splice(k, 1) if(_.isRealObject(m)) delete m[k]; @@ -48,7 +48,7 @@ class Json extends Visitor { **/ _clean(current, memo = {}) { let keys = Object.getOwnPropertyNames(current); - return _.reduce(keys, (m, k) => this._reduce(m, current[k], k), memo, this); + return _.reduce(keys, (m, k) => this._filterObject(m, current[k], k), memo, this); } /** diff --git a/package.json b/package.json index c728c03..09ddcc3 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,8 @@ "mocha": "2.5.3", "mocha-lcov-reporter": "1.3.0", "nyc": "10.1.2", - "sinon": "1.17.7" + "sinon": "1.17.7", + "sinon-stub-promise": "4.0.0" }, "dependencies": { "acorn": "4.0.11", diff --git a/test/global.js b/test/global.js index 6511fbb..e9b957f 100644 --- a/test/global.js +++ b/test/global.js @@ -8,3 +8,4 @@ global._ = require('underscore'); global.sinon = require('sinon'); global.assert = require('chai').assert; global.basepath = path.join(path.resolve(__dirname, '..'), 'lib'); +require('sinon-stub-promise')(sinon); diff --git a/test/lib/visitors/configuration.spec.es6 b/test/lib/visitors/configuration.spec.es6 index bd45eb6..2e783eb 100644 --- a/test/lib/visitors/configuration.spec.es6 +++ b/test/lib/visitors/configuration.spec.es6 @@ -6,6 +6,7 @@ import Configuration from 'visitors/configuration'; import Command from 'command'; import Factory from 'util/factory/factory'; import QueueAsync from 'util/adt/queue-async'; +import logger, { Logger } from 'util/logger/logger'; describe('visitors.Configuration', function() { @@ -18,8 +19,9 @@ describe('visitors.Configuration', function() { beforeEach(() => { this.mockProto = this.sandbox.mock(Configuration.prototype); this.mockQueueAsync = this.sandbox.mock(QueueAsync.prototype); - this.mockCommand = this.sandbox.mock(Command.prototype); this.mockFactory = this.sandbox.mock(Factory); + this.mockCommand = this.sandbox.mock(Command.prototype); + this.mockLogger = this.sandbox.mock(Logger.prototype); }); afterEach(() => { @@ -27,6 +29,7 @@ describe('visitors.Configuration', function() { this.mockQueueAsync.verify(); this.mockFactory.verify(); this.mockCommand.verify(); + this.mockLogger.verify(); this.sandbox.restore(); @@ -34,6 +37,7 @@ describe('visitors.Configuration', function() { delete this.mockQueueAsync; delete this.mockFactory; delete this.mockCommand; + delete this.mockLogger; }); after(() => { @@ -102,62 +106,251 @@ describe('visitors.Configuration', function() { describe('_format()', () => { - xit('Should apply formatter to the current configuration option', () => {}); - xit('Should NOT apply formatter to the current configuration option (Factory doesn\'t exists)', () => {}); + it('Should apply formatter to the current configuration option', () => { + const options = { scan: './src/**', extensions: '.js,.es6' }; + const expTransform = ['.js', '.es6']; + const formatterPath = 'visitors/configuration/formatter/extensions'; + const expFormatterPath = this.mockProto.expects('formatterPath') + .once() + .withArgs('extensions') + .returns(formatterPath); + + const expGet = this.mockFactory.expects('get') + .once() + .withArgs(formatterPath, options.extensions) + .returns(expTransform); + + const expExists = this.mockFactory.expects('exists') + .once() + .withArgs(formatterPath) + .returns(true); + + const exp = this.configuration._format(options, options.extensions, 'extensions'); + assert.isTrue(expFormatterPath.calledBefore(expExists)); + assert.isTrue(expExists.calledBefore(expGet)); + assert.isObject(exp); + assert.property(exp, 'scan'); + assert.property(exp, 'extensions'); + assert.equal(expTransform, exp.extensions); + }); + + it('Should NOT apply formatter to the current configuration option', () => { + const options = { scan: './src/**' }; + const formatterPath = 'visitors/configuration/formatter/scan'; + const expFormatterPath = this.mockProto.expects('formatterPath') + .once() + .withArgs('scan') + .returns(formatterPath); + + const expGet = this.mockFactory.expects('get').never(); + + const expExists = this.mockFactory.expects('exists') + .once() + .withArgs(formatterPath) + .returns(false); + + const exp = this.configuration._format(options, options.scan, 'scan'); + assert.isTrue(expFormatterPath.calledBefore(expExists)); + assert.isObject(exp); + assert.property(exp, 'scan'); + assert.equal(options.scan, exp.scan); + }); }); describe('_source()', () => { - xit('Should apply source configuration options to the current command', () => {}); + it('Should apply source configuration options to the current command', () => { + const options = { scan: './src/**', extensions: ['.js', '.es6'], unrecognized: true }; + assert.instanceOf(this.configuration._source(options), Configuration); + + assert.property(this.command, 'scan'); + assert.property(this.command, 'extensions'); + assert.notProperty(this.command, 'unrecognized'); + + delete this.command.scan; + delete this.command.extensions; + }); }); describe('_target()', () => { - xit('Should apply target configuration options to the current command', () => {}); + it('Should apply target configuration options to the current command', () => { + const options = { cjs: { destination: './dist', format: 'cjs', unrecognized: false }, unrecognized: true }; + assert.instanceOf(this.configuration._target(options), Configuration); + + assert.property(this.command, 'target'); + assert.property(this.command.target, 'cjs'); + assert.notProperty(this.command.target.cjs, 'unrecognized'); + }); }); describe('_logger()', () => { - xit('Should apply logger configuration option to the singleton logger', () => {}); + it('Should apply logger configuration option to the singleton logger', () => { + assert.instanceOf(this.configuration._logger('silent'), Configuration); + logger.level(Logger.level.output); + }); }); describe('_override()', () => { - xit('Should override configuration options with cli options', () => {}); + it('Should override configuration options with cli options', () => { + const options = { scan: './src/**', extensions: ['.js', '.es6'], unrecognized: true }; + const filtered = _.omit(options, 'unrecognized'); + const expFormat = this.mockProto.expects('_format') + .exactly(2) + .withArgs(filtered, sinon.match.any, sinon.match.string) + .returns(filtered); + + assert.instanceOf(this.configuration._override(options), Configuration); + assert.property(this.command, 'scan'); + assert.property(this.command, 'extensions'); + assert.notProperty(this.command, 'unrecognized'); + + delete this.command.scan; + delete this.command.extensions; + }); }); describe('formatterPath()', () => { - xit('Should resolve formatter path to the factory', () => {}); + it('Should resolve formatter path to the factory', () => { + const exp = 'visitors/configuration/formatter/target'; + assert.equal(exp, this.configuration.formatterPath('target')); + }); }); describe('onParse()', () => { - xit('Should iterate over results gatter from commander to perform transformations', () => {}); + it('Should iterate over results gather from commander to perform transformations', () => { + const expResults = [{ option: 1 }, { option: 2 }, null]; + const expOnOptions = this.mockProto.expects('onOptions') + .exactly(2) + .withArgs(sinon.match.object) + .returns(this.configuration); + + assert.instanceOf(this.configuration.onParse(expResults), Configuration); + }); }); describe('onParseError()', () => { - xit('Should output possible warning', () => {}); + it('Should output possible warning', () => { + const expMessage = 'Warning Message'; + const expLogger = this.mockLogger.expects('_add') + .once() + .withArgs(expMessage) + .returns(logger); + + const expWarn = this.mockLogger.expects('warn') + .once() + .returns(logger); + + assert.instanceOf(this.configuration.onParseError(expMessage), Configuration); + }); }); describe('onOptions()', () => { - xit('Should perform options transformations', () => {}); + it('Should perform options transformations', () => { + const expResult = { + source: { scan: './src/**' }, + target: { cjs: { destination: './dist', format: 'cjs' } }, + logLevel: 'silent' + }; + const expSource = this.mockProto.expects('_source') + .once() + .withArgs(expResult.source) + .returns(this.configuration); + const expTarget = this.mockProto.expects('_target') + .once() + .withArgs(expResult.target) + .returns(this.configuration); + const expLogger = this.mockProto.expects('_logger') + .once() + .withArgs(expResult.logLevel) + .returns(this.configuration); + const expOverride = this.mockProto.expects('_override') + .once() + .withArgs(this.command.options) + .returns(this.configuration); + + assert.instanceOf(this.configuration.onOptions(expResult), Configuration); + }); + + it('Should NOT perform option transformations (Parse Error)', () => { + const expResult = { warn: 'Warning Message' }; + + const expParseError = this.mockProto.expects('onParseError') + .once() + .withArgs(expResult.warn) + .returns(this.configuration); + const expSource = this.mockProto.expects('_source').never(); + const expTarget = this.mockProto.expects('_target').never(); + const expLogger = this.mockProto.expects('_logger').never(); + const expOverride = this.mockProto.expects('_override').never(); + + assert.instanceOf(this.configuration.onOptions(expResult), Configuration); + }); }); describe('parse()', () => { - xit('Should parse commander options and apply configuration based on input', () => {}); + it('Should create methods and resolve configuration option source', () => { + const expResult = { source: { scan: './src/**' } }; + const queueStubPromise = this.sandbox.stub().returnsPromise(); + const expCreate = this.mockProto.expects('_create') + .exactly(2) + .withArgs(undefined) + .returns(this.configuration); + + const expOnParse = this.mockProto.expects('onParse') + .once() + .withArgs(expResult) + .returns(this.configuration); + + const expOnParseError = this.mockProto.expects('onParseError').never(); + + const expPoll = this.mockQueueAsync.expects('poll') + .once() + .returns(queueStubPromise.resolves(expResult)()); + + const exp = this.configuration.parse(); + assert.isTrue(exp.resolved); + assert.instanceOf(exp.resolveValue, Configuration); + }); + + it('Should create methods and reject configuration option (warn message from metthod)', () => { + const expResult = { warn: 'Warning Message' }; + let queueStubPromise = this.sandbox.stub().returnsPromise(); + const expCreate = this.mockProto.expects('_create') + .exactly(2) + .withArgs(undefined, sinon.match.string) + .returns(this.configuration); + + const expOnParse = this.mockProto.expects('onParse').never(); + + const expOnParseError = this.mockProto.expects('onParseError') + .once() + .withArgs(expResult.warn) + .returns(this.configuration); + + const expPoll = this.mockQueueAsync.expects('poll') + .once() + .returns(queueStubPromise.rejects(expResult.warn)()); + + const exp = this.configuration.parse(); + assert.instanceOf(exp.resolveValue, Configuration); + }); }); From e199b7985cce9394e6e37a9c900dc666066c12c3 Mon Sep 17 00:00:00 2001 From: kuakman <3dimentionar@gmail.com> Date: Wed, 22 Mar 2017 08:56:46 -0700 Subject: [PATCH 34/39] cli: checkpoint - added unit test coverage to visitors.configuration.Local. --- lib/command.es6 | 9 + lib/visitors/configuration.es6 | 7 +- lib/visitors/configuration/local.es6 | 37 +-- lib/visitors/configuration/remote.es6 | 7 +- test/lib/visitors/configuration.spec.es6 | 8 +- .../lib/visitors/configuration/local.spec.es6 | 210 ++++++++++++++++++ .../visitors/configuration/remote.spec.es6 | 39 ++++ 7 files changed, 292 insertions(+), 25 deletions(-) diff --git a/lib/command.es6 b/lib/command.es6 index f685ac6..4dedf69 100644 --- a/lib/command.es6 +++ b/lib/command.es6 @@ -184,6 +184,15 @@ class Command extends Visited { return _.defined(predicate) && _.isFunction(predicate) ? _.map(this.target, predicate, this) : []; } + /** + * Retrieves command options + * @public + * @return {Object} + **/ + getOptions() { + return this.options; + } + /** * Command Visitors * @static diff --git a/lib/visitors/configuration.es6 b/lib/visitors/configuration.es6 index 3215fce..ef46199 100644 --- a/lib/visitors/configuration.es6 +++ b/lib/visitors/configuration.es6 @@ -30,12 +30,11 @@ class Configuration extends Visitor { /** * Create configuration retrieval method * @private - * @param {On} * @param {String} method - configuration method path * @return {util.visitor.Visitor} **/ - _create(options, method) { - this.queue.offer(Factory.get(method, this.command, options)); + _create(method) { + this.queue.offer(Factory.get(method, this.command)); return this; } @@ -122,7 +121,7 @@ class Configuration extends Visitor { * @return {Promise} **/ parse() { - Configuration.methods.forEach(_.bind(this._create, this, this.command.options)); + Configuration.methods.forEach(_.bind(this._create, this)); return this.queue.poll().then(_.bind(this.onParse, this), _.bind(this.onParseError, this)); } diff --git a/lib/visitors/configuration/local.es6 b/lib/visitors/configuration/local.es6 index 59ba7e0..16e3af8 100644 --- a/lib/visitors/configuration/local.es6 +++ b/lib/visitors/configuration/local.es6 @@ -21,11 +21,10 @@ class Local extends Visited { * Constructor * @public * @param {Command} command - command reference - * @param {Object} options - command options * @return {visitors.configuration.Local} **/ - constructor(command, options) { - return super({ command, options }); + constructor(command) { + return super({ command }); } /** @@ -48,7 +47,7 @@ class Local extends Visited { try { return _.defined(file) && _.defined(fs.statSync(file)); } catch(ex) { - // TODO: logger.debug(); + logger(`LocalConfiguration: ${ex.message}`).debug({}, logger.yellow); return false; } } @@ -63,26 +62,37 @@ class Local extends Visited { try { return fs.readJsonSync(path); } catch(ex) { - // TODO: logger.debug(); + logger(`LocalConfiguration: ${ex.message}`).debug({}, logger.yellow); return null; } } /** - * Will try to open configuration as javascript via module.export + * Will try to open configuration as javascript via module.exports * @public * @param {String} path - resolved path to configuration * @return {Object} **/ tryJs(path) { try { - return eval(fs.readFileSync(path, { encoding: 'utf8' }).toString()); + return require(path); } catch(ex) { - // TODO: logger.debug(); + logger(`LocalConfiguration: ${ex.message}`).debug({}, logger.yellow); return null; } } + /** + * Outputs a warn message and returns the object + * @public + * @param {String} message - warning message + * @return {Object} + **/ + warn(warn) { + logger(warn).debug({}, logger.yellow); + return { warn }; + } + /** * Load local configuration file * @public @@ -91,9 +101,9 @@ class Local extends Visited { **/ load(name) { let file = this.resolvePath(name), output = ''; - if(this.exists(file) && (output = (this.tryJson(file) || this.tryJs(file)))) return output; - // logger.debug() - return { warn: Local.messages.notFound }; + if(!this.exists(file)) return this.warn(Local.messages.notFound); + if(!(output = (this.tryJson(file) || this.tryJs(file)))) return this.warn(Local.messages.invalid); + return output; } /** @@ -106,7 +116,7 @@ class Local extends Visited { * @return {Promise} **/ next(adt, resolve, reject) { - const { config } = this.options; + const { config } = this.command.getOptions(); return resolve(this.load(config)); } @@ -116,7 +126,8 @@ class Local extends Visited { * @type {Object} **/ static messages = { - notFound: `Local configuration not found` + notFound: `Local configuration not found`, + invalid: `Local configuration: sqbox file is invalid` } } diff --git a/lib/visitors/configuration/remote.es6 b/lib/visitors/configuration/remote.es6 index cffb916..906bf38 100644 --- a/lib/visitors/configuration/remote.es6 +++ b/lib/visitors/configuration/remote.es6 @@ -19,11 +19,10 @@ class Remote extends Visited { * Constructor * @public * @param {Command} command - command reference - * @param {Object} options - command options * @return {visitors.configuration.Remote} **/ - constructor(command, options) { - return super({ command, options }); + constructor(command) { + return super({ command }); } /** @@ -79,7 +78,7 @@ class Remote extends Visited { * @return {Promise} **/ next(adt, resolve, reject) { - const { url } = this.options; + const { url } = this.command.getOptions(); this.once(Remote.events.load, resolve); return _.defined(url) ? this.load(url) : this.out(null); } diff --git a/test/lib/visitors/configuration.spec.es6 b/test/lib/visitors/configuration.spec.es6 index 2e783eb..eadaa94 100644 --- a/test/lib/visitors/configuration.spec.es6 +++ b/test/lib/visitors/configuration.spec.es6 @@ -90,7 +90,7 @@ describe('visitors.Configuration', function() { const options = { option: true }; const expGet = this.mockFactory.expects('get') .once() - .withArgs('visitors/configuration/remote', this.command, options) + .withArgs('visitors/configuration/remote') .returns(options); const expOffer = this.mockQueueAsync.expects('offer') @@ -98,7 +98,7 @@ describe('visitors.Configuration', function() { .withArgs(options) .returns(this.configuration.queue); - const exp = this.configuration._create(options, 'visitors/configuration/remote'); + const exp = this.configuration._create('visitors/configuration/remote'); assert.instanceOf(exp, Configuration); }); @@ -310,7 +310,7 @@ describe('visitors.Configuration', function() { const queueStubPromise = this.sandbox.stub().returnsPromise(); const expCreate = this.mockProto.expects('_create') .exactly(2) - .withArgs(undefined) + .withArgs(sinon.match.string) .returns(this.configuration); const expOnParse = this.mockProto.expects('onParse') @@ -334,7 +334,7 @@ describe('visitors.Configuration', function() { let queueStubPromise = this.sandbox.stub().returnsPromise(); const expCreate = this.mockProto.expects('_create') .exactly(2) - .withArgs(undefined, sinon.match.string) + .withArgs(sinon.match.string) .returns(this.configuration); const expOnParse = this.mockProto.expects('onParse').never(); diff --git a/test/lib/visitors/configuration/local.spec.es6 b/test/lib/visitors/configuration/local.spec.es6 index e69de29..37d4ff8 100644 --- a/test/lib/visitors/configuration/local.spec.es6 +++ b/test/lib/visitors/configuration/local.spec.es6 @@ -0,0 +1,210 @@ +/** +* @module visitors.configuration +* @author Patricio Ferreira <3dimentionar@gmail.com> +**/ +import Local from 'visitors/configuration/local'; +import Command from 'command'; +import QueueAsync from 'util/adt/queue-async'; +import logger, { Logger } from 'util/logger/logger'; + +describe('visitors.configuration.Local', function() { + + before(() => { + this.local = null; + this.options = { config: 'test/specs/.sqboxrc' }; + this.command = Command.new(); + this.configPath = path.resolve(this.command.cwd, this.options.config); + this.sandbox = sinon.sandbox.create(); + }); + + beforeEach(() => { + this.mockProto = this.sandbox.mock(Local.prototype); + this.mockCommand = this.sandbox.mock(Command.prototype); + this.mockLogger = this.sandbox.mock(Logger.prototype); + this.mockFs = this.sandbox.mock(fs); + }); + + afterEach(() => { + this.mockProto.verify(); + this.mockCommand.verify(); + this.mockLogger.verify(); + this.mockFs.verify(); + + this.sandbox.restore(); + + delete this.mockProto; + delete this.mockCommand; + delete this.mockLogger; + delete this.mockFs; + }); + + after(() => { + delete this.local; + delete this.options; + delete this.command; + delete this.configPath; + delete this.sandbox; + }); + + describe('constructor()', () => { + + it('Should get a new instance', () => { + this.local = Local.new(this.command, this.options); + assert.instanceOf(this.local, Local); + }); + + }); + + describe('resolvePath()', () => { + + it('Should resolve absolute path to a file', () => { + const exp = this.local.resolvePath(this.options.config); + assert.equal(this.configPath, exp); + }); + + }); + + describe('exists()', () => { + + it('Should return true: file exists', () => { + const expStatSync = this.mockFs.expects('statSync') + .once() + .withArgs(this.configPath) + .returns({}); + + assert.isTrue(this.local.exists(this.configPath)); + }); + + it('Should return false: file doesn\'t exists (file is undefined)', () => { + const expStatSync = this.mockFs.expects('statSync').never(); + assert.isFalse(this.local.exists()); + }); + + it('Should return false: file doesn\'t exists (file not found in path)', () => { + const expStatSync = this.mockFs.expects('statSync') + .once() + .withArgs(this.configPath) + .throws(new Error('Not Found')); + + assert.isFalse(this.local.exists(this.configPath)); + }); + + }); + + describe('tryJson()', () => { + + it('Should return an object: valid json', () => { + const expReadJsonSync = this.mockFs.expects('readJsonSync') + .once() + .withArgs(this.configPath) + .returns({ valid: true }); + assert.isObject(this.local.tryJson(this.configPath)); + }); + + it('Should return null: invalid json', () => { + const expReadJsonSync = this.mockFs.expects('readJsonSync') + .once() + .withArgs(this.configPath) + .throws(new Error('Invalid JSON')); + assert.isNull(this.local.tryJson(this.configPath)); + }); + + }); + + describe('tryJs()', () => { + + it('Should return an object: valid javascript', () => { + const jspath = path.resolve(this.command.cwd, 'test/specs/.sqbox.js'); + assert.isObject(this.local.tryJs(jspath)); + }); + + it('Should return null: invalid javascript (file not found)', () => { + const jspath = path.resolve(this.command.cwd, 'test/specs/.sqbox.ext'); + assert.isNull(this.local.tryJs(jspath)); + }); + + }); + + describe('load()', () => { + + it('Should load configuration file', () => { + const input = { source: { scan: './src/**' } }; + const expExists = this.mockProto.expects('exists') + .once() + .withArgs(sinon.match.string) + .returns(true); + + const expTryJson = this.mockProto.expects('tryJson') + .once() + .withArgs(sinon.match.string) + .returns(input); + + const exp = this.local.load(this.options.config); + assert.isObject(exp); + assert.deepEqual(input, exp); + }); + + it('Should NOT load configuration file (file doesn\'t exists)', () => { + const input = { warn: Local.messages.notFound }; + const expExists = this.mockProto.expects('exists') + .once() + .withArgs(sinon.match.string) + .returns(false); + + const expTryJson = this.mockProto.expects('tryJson').never(); + const expTryJs = this.mockProto.expects('tryJs').never(); + + const exp = this.local.load(this.options.config); + assert.isObject(exp); + assert.deepEqual(input, exp); + }); + + it('Should NOT load configuration file (Invalid Json && Javascript)', () => { + const input = { warn: Local.messages.invalid }; + const expExists = this.mockProto.expects('exists') + .once() + .withArgs(sinon.match.string) + .returns(true); + + const expTryJson = this.mockProto.expects('tryJson') + .once() + .withArgs(sinon.match.string) + .returns(null); + + const expTryJs = this.mockProto.expects('tryJs') + .once() + .withArgs(sinon.match.string) + .returns(null); + + const exp = this.local.load(this.options.config); + assert.isObject(exp); + assert.deepEqual(input, exp); + }); + + }); + + describe('next()', () => { + + it('Should resolve promise for asynchronous operation', () => { + const expResolved = { result: true }; + const resolvePromise = this.sandbox.stub().returnsPromise(); + const spyReject = this.sandbox.spy(); + + const expGetOptions = this.mockCommand.expects('getOptions') + .once() + .returns(this.options); + + const expLoad = this.mockProto.expects('load') + .once() + .withArgs(this.options.config) + .returns(expResolved); + + const exp = this.local.next({}, resolvePromise.resolves(expResolved), spyReject); + assert.isTrue(exp.resolved); + assert.deepEqual(expResolved, exp.resolveValue); + assert.isTrue(spyReject.notCalled); + }); + + }); + +}); diff --git a/test/lib/visitors/configuration/remote.spec.es6 b/test/lib/visitors/configuration/remote.spec.es6 index e69de29..ea8235d 100644 --- a/test/lib/visitors/configuration/remote.spec.es6 +++ b/test/lib/visitors/configuration/remote.spec.es6 @@ -0,0 +1,39 @@ +/** +* @module visitors.configuration +* @author Patricio Ferreira <3dimentionar@gmail.com> +**/ +import Remote from 'visitors/configuration/remote'; +import Command from 'command'; +import logger, { Logger } from 'util/logger/logger'; + +describe('visitors.configuration.Remote', function() { + + before(() => { + this.command = Command.new(); + this.sandbox = sinon.sandbox.create(); + }); + + beforeEach(() => { + this.mockProto = this.sandbox.mock(Remote.prototype); + this.mockCommand = this.sandbox.mock(Command.prototype); + this.mockLogger = this.sandbox.mock(Logger.prototype); + }); + + afterEach(() => { + this.mockProto.verify(); + this.mockCommand.verify(); + this.mockLogger.verify(); + + this.sandbox.restore(); + + delete this.mockProto; + delete this.mockCommand; + delete this.mockLogger; + }); + + after(() => { + delete this.command; + delete this.sandbox; + }); + +}); From accf9c7d11caad2c4d69354fa95240f59f9611f3 Mon Sep 17 00:00:00 2001 From: kuakman <3dimentionar@gmail.com> Date: Wed, 22 Mar 2017 09:30:13 -0700 Subject: [PATCH 35/39] cli: checkpoint - fixed unit test cases on visitors.configuration.Local for next() asynchronous method. --- test/lib/visitors/configuration/local.spec.es6 | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/test/lib/visitors/configuration/local.spec.es6 b/test/lib/visitors/configuration/local.spec.es6 index 37d4ff8..c42ba87 100644 --- a/test/lib/visitors/configuration/local.spec.es6 +++ b/test/lib/visitors/configuration/local.spec.es6 @@ -185,7 +185,7 @@ describe('visitors.configuration.Local', function() { describe('next()', () => { - it('Should resolve promise for asynchronous operation', () => { + it('Should resolve promise for asynchronous operation', (done) => { const expResolved = { result: true }; const resolvePromise = this.sandbox.stub().returnsPromise(); const spyReject = this.sandbox.spy(); @@ -199,10 +199,13 @@ describe('visitors.configuration.Local', function() { .withArgs(this.options.config) .returns(expResolved); - const exp = this.local.next({}, resolvePromise.resolves(expResolved), spyReject); - assert.isTrue(exp.resolved); - assert.deepEqual(expResolved, exp.resolveValue); - assert.isTrue(spyReject.notCalled); + const exp = this.local.next({}, _.bind(resolvePromise.resolves, resolvePromise), spyReject)(); + exp.then((result) => { + assert.isTrue(exp.resolved); + assert.deepEqual(expResolved, exp.resolveValue); + assert.isTrue(spyReject.notCalled); + done(); + }); }); }); From f969d6c0048528b6b457805f5f85d34345324cf1 Mon Sep 17 00:00:00 2001 From: kuakman <3dimentionar@gmail.com> Date: Sat, 25 Mar 2017 16:09:25 -0700 Subject: [PATCH 36/39] cli: checkpoint - Added unit test coverage to visitors.configuration.Local and Remote. --- lib/visitors/async/async.es6 | 5 +- lib/visitors/configuration/remote.es6 | 72 ++++------ package.json | 1 + test/lib/visitors/async/async.spec.es6 | 2 +- .../lib/visitors/configuration/local.spec.es6 | 9 +- .../visitors/configuration/remote.spec.es6 | 135 ++++++++++++++++++ 6 files changed, 173 insertions(+), 51 deletions(-) diff --git a/lib/visitors/async/async.es6 b/lib/visitors/async/async.es6 index 27b5e19..c9b7038 100644 --- a/lib/visitors/async/async.es6 +++ b/lib/visitors/async/async.es6 @@ -31,10 +31,11 @@ class Asynchronous extends Visitor { * @param adt {util.proxy.Asynchronous} adt used for asynchronous operations * @param resolve {Function} asynchronous promise's resolve * @param reject {Function} asynchronous promise's reject - * @return {Promise} + * @return {visitors.async.Asynchronous} **/ next(adt, resolve, reject) { - return resolve(); + resolve(); + return this; } /** diff --git a/lib/visitors/configuration/remote.es6 b/lib/visitors/configuration/remote.es6 index 906bf38..63256bb 100644 --- a/lib/visitors/configuration/remote.es6 +++ b/lib/visitors/configuration/remote.es6 @@ -4,7 +4,7 @@ **/ import _ from 'util/mixins'; import extend from 'extend'; -import request from 'request'; +import request from 'request-promise'; import Visited from 'util/visitor/visited'; /** @@ -28,44 +28,39 @@ class Remote extends Visited { /** * Load remote configuration file from url * @public - * @param {String} url - url - * @return {Object} + * @param {String} url - url to load remote configuration from + * @param resolve {Function} asynchronous promise's resolve + * @param reject {Function} asynchronous promise's reject + * @return {Promise} **/ - load(url) { - request(url, _.bind(this.onLoad, this)); - return this; + load(url, resolve, reject) { + return request.get(url, { json: true }) + .then(_.bind(this.onResponse, this, resolve, reject)) + .catch(_.bind(this.onResponseError, this, resolve, reject)); } /** - * Remote Load Handler + * Remote Response Success * @public - * @param {Error} [err] - request error - * @param {Object} response - request response reference - * @param {String} body - request error - * @return {Boolean} + * @param resolve {Function} asynchronous promise's resolve + * @param reject {Function} asynchronous promise's reject + * @param {Object} response - request response + * @return {visitors.configuration.Remote} **/ - onLoad(err, response, body) { - let output = {}; - try { - if(_.defined(err)) - return this.out({ warn: Remote.messages.error({ warn: err }) }); - if(response.statusCode !== 200) - return this.out({ warn: Remote.messages.error({ warn: response.statusCode }) }); - this.out(JSON.parse(body)); - } catch(ex) { - // logger.debug(); - this.out({ warn: (Remote.messages.invalid + ' - ' + ex.message) }); - } + onResponse(resolve, reject, response) { + return resolve(response); } /** - * Result Output + * Remote Response Error * @public - * @param {Object} output - output to dispatch - * @return {Boolean} + * @param resolve {Function} asynchronous promise's resolve + * @param reject {Function} asynchronous promise's reject + * @param {Object} err - response error + * @return {Object} **/ - out(output) { - return this.emit(Remote.events.load, output); + onResponseError(resolve, reject, err) { + return resolve({ warn: Remote.messages.error({ err }) }); } /** @@ -79,20 +74,9 @@ class Remote extends Visited { **/ next(adt, resolve, reject) { const { url } = this.command.getOptions(); - this.once(Remote.events.load, resolve); - return _.defined(url) ? this.load(url) : this.out(null); - } - - /** - * Remote Events - * @static - * @type {Object} - **/ - static events = { - /** - * @event load - **/ - load: 'visitors:configuration:remote:load' + return _.defined(url) ? + this.load(url, resolve, reject) : + resolve({ warn: Remote.messages.noUrl }); } /** @@ -101,8 +85,8 @@ class Remote extends Visited { * @type {Object} **/ static messages = { - error: _.template(`Remote Configuration - <%= warn %>`), - invalid: `Invalid JSON format` + noUrl: `No Url specified`, + error: _.template(`Remote Url Error - <%= err %>`) } } diff --git a/package.json b/package.json index 09ddcc3..16e4d6c 100644 --- a/package.json +++ b/package.json @@ -64,6 +64,7 @@ "minimatch": "3.0.3", "node-watch": "0.4.1", "request": "2.79.0", + "request-promise": "4.2.0", "underscore": "1.8.3", "underscore.string": "3.3.4", "yargs": "6.6.0" diff --git a/test/lib/visitors/async/async.spec.es6 b/test/lib/visitors/async/async.spec.es6 index 518d3ba..979e671 100644 --- a/test/lib/visitors/async/async.spec.es6 +++ b/test/lib/visitors/async/async.spec.es6 @@ -43,7 +43,7 @@ describe('visitors.async.Asynchronous', function() { it('Should call promise\'s resolve as a default strategy', () => { const resolveSpy = this.sandbox.spy(); const exp = Asynchronous.new({}); - exp.next({}, resolveSpy); + assert.instanceOf(exp.next({}, resolveSpy), Asynchronous); assert.isTrue(resolveSpy.calledOnce); }); diff --git a/test/lib/visitors/configuration/local.spec.es6 b/test/lib/visitors/configuration/local.spec.es6 index c42ba87..1d58c44 100644 --- a/test/lib/visitors/configuration/local.spec.es6 +++ b/test/lib/visitors/configuration/local.spec.es6 @@ -49,8 +49,9 @@ describe('visitors.configuration.Local', function() { describe('constructor()', () => { it('Should get a new instance', () => { - this.local = Local.new(this.command, this.options); + this.local = Local.new(this.command); assert.instanceOf(this.local, Local); + assert.property(this.local, 'command'); }); }); @@ -188,7 +189,7 @@ describe('visitors.configuration.Local', function() { it('Should resolve promise for asynchronous operation', (done) => { const expResolved = { result: true }; const resolvePromise = this.sandbox.stub().returnsPromise(); - const spyReject = this.sandbox.spy(); + const spyResolve = this.sandbox.spy(); const expGetOptions = this.mockCommand.expects('getOptions') .once() @@ -199,11 +200,11 @@ describe('visitors.configuration.Local', function() { .withArgs(this.options.config) .returns(expResolved); - const exp = this.local.next({}, _.bind(resolvePromise.resolves, resolvePromise), spyReject)(); + const exp = this.local.next({}, _.bind(resolvePromise.resolves, resolvePromise), spyResolve)(); exp.then((result) => { assert.isTrue(exp.resolved); assert.deepEqual(expResolved, exp.resolveValue); - assert.isTrue(spyReject.notCalled); + assert.isTrue(spyResolve.notCalled); done(); }); }); diff --git a/test/lib/visitors/configuration/remote.spec.es6 b/test/lib/visitors/configuration/remote.spec.es6 index ea8235d..bd0a22d 100644 --- a/test/lib/visitors/configuration/remote.spec.es6 +++ b/test/lib/visitors/configuration/remote.spec.es6 @@ -4,11 +4,15 @@ **/ import Remote from 'visitors/configuration/remote'; import Command from 'command'; +import QueueAsync from 'util/adt/queue-async'; +import request from 'request-promise'; import logger, { Logger } from 'util/logger/logger'; describe('visitors.configuration.Remote', function() { before(() => { + this.remote = null; + this.options = { url: 'http://squarebox.nahuel.io/.sqboxrc' }; this.command = Command.new(); this.sandbox = sinon.sandbox.create(); }); @@ -32,8 +36,139 @@ describe('visitors.configuration.Remote', function() { }); after(() => { + delete this.remote; + delete this.options; delete this.command; delete this.sandbox; }); + describe('constructor()', () => { + + it('Should get a new instance', () => { + this.remote = Remote.new(this.command); + assert.instanceOf(this.remote, Remote); + assert.property(this.remote, 'command'); + }); + + }); + + describe('onResponse()', () => { + + it('Should resolve promise successfuly', (done) => { + const output = { option: true }; + const expPromise = this.sandbox.stub().returnsPromise(); + const bindResolves = _.bind(expPromise.resolves, expPromise); + const bindRejects = _.bind(expPromise.rejects, expPromise); + + const exp = this.remote.onResponse(bindResolves, bindRejects, output)(); + exp.then((result) => { + assert.equal(output, result); + done(); + }); + }); + + }); + + describe('onResponseError()', () => { + + it('Should resolve promise with Error', () => { + const err = 'Remote Error'; + const output = { warn: Remote.messages.error({ err }) }; + const expPromise = this.sandbox.stub().returnsPromise(); + const bindResolves = _.bind(expPromise.resolves, expPromise); + const bindRejects = _.bind(expPromise.rejects, expPromise); + + const exp = this.remote.onResponseError(bindResolves, bindRejects, err)(); + exp.then((result) => { + assert.deepEqual(output, result); + done(); + }); + }); + + }); + + describe('load()', () => { + + it('Should execute a remote request (resolves)', () => { + const output = { option: true }; + const expPromise = this.sandbox.stub(request, 'get').returnsPromise(); + + const spyInnerResolve = this.sandbox.spy(); + const spyInnerReject = this.sandbox.spy(); + + const expOnResponseError = this.mockProto.expects('onResponseError').never(); + const expOnResponse = this.mockProto.expects('onResponse') + .once() + .withArgs(spyInnerResolve, spyInnerReject, output) + .returns(spyInnerResolve(output)); + + const exp = this.remote.load(this.options.url, spyInnerResolve, spyInnerReject); + expPromise.resolves(output); + + assert.isTrue(spyInnerResolve.called); + assert.isFalse(spyInnerReject.called); + }); + + it('Should execute a remote request (rejects)', () => { + const output = 'Remote Error'; + const expPromise = this.sandbox.stub(request, 'get').returnsPromise(); + + const spyInnerResolve = this.sandbox.spy(); + const spyInnerReject = this.sandbox.spy(); + + const expOnResponse = this.mockProto.expects('onResponse').never(); + const expOnResponseError = this.mockProto.expects('onResponseError') + .once() + .withArgs(spyInnerResolve, spyInnerReject, output) + .returns(spyInnerResolve(output)); + + const exp = this.remote.load(this.options.url, spyInnerResolve, spyInnerReject); + expPromise.rejects(output); + + assert.isTrue(spyInnerResolve.called); + assert.isFalse(spyInnerReject.called); + }); + + }); + + describe('next()', () => { + + it('Should resolve promise with not warning (Url available)', () => { + const output = { option: true }; + const spyResolve = this.sandbox.spy(); + const spyReject = this.sandbox.spy(); + + const expGetOptions = this.mockCommand.expects('getOptions') + .once() + .returns(this.options); + + const expLoad = this.mockProto.expects('load') + .once() + .withArgs(this.options.url, spyResolve, spyReject) + .returns(spyResolve(output)); + + const exp = this.remote.next({}, spyResolve, spyReject); + + assert.equal(output.option, spyResolve.firstCall.args[0].option); + assert.isTrue(spyResolve.called); + assert.isFalse(spyReject.called); + }); + + it('Should resolve promise with warning (No Url available)', () => { + const spyResolve = this.sandbox.spy(); + const spyReject = this.sandbox.spy(); + + const expGetOptions = this.mockCommand.expects('getOptions') + .once() + .returns({}); + + const exp = this.remote.next({}, spyResolve, spyReject); + + assert.equal(Remote.messages.noUrl, spyResolve.firstCall.args[0].warn); + assert.isTrue(spyResolve.called); + assert.isFalse(spyReject.called); + }); + + }); + }); From 85d538fe1ecf3f99a1ca9c14bedd90b3aef76c91 Mon Sep 17 00:00:00 2001 From: kuakman <3dimentionar@gmail.com> Date: Sun, 26 Mar 2017 09:25:08 -0700 Subject: [PATCH 37/39] cli: checkpoint - Added unit test coverage to visitors.commander Helpers. --- lib/visitors/commander/version.es6 | 2 +- test/lib/visitors/commander/commands.spec.es6 | 103 ++++++++++++++++++ test/lib/visitors/commander/epilogue.spec.es6 | 44 ++++++++ test/lib/visitors/commander/parse.spec.es6 | 55 ++++++++++ test/lib/visitors/commander/usage.spec.es6 | 44 ++++++++ test/lib/visitors/commander/version.spec.es6 | 66 +++++++++++ 6 files changed, 313 insertions(+), 1 deletion(-) diff --git a/lib/visitors/commander/version.es6 b/lib/visitors/commander/version.es6 index 990bb34..4d9da36 100644 --- a/lib/visitors/commander/version.es6 +++ b/lib/visitors/commander/version.es6 @@ -17,7 +17,7 @@ export default (yargs, command) => { try { return yargs.version(require(resolve(command.dirname, '..', 'package.json')).version); } catch(ex) { - logger(`[WARN] SquareBox Version not detected caused by ${ex.message}`).debug(logger.yellow); + logger(`[WARN] SquareBox Version not detected - ${ex.message}`).debug(logger.yellow); } return yargs; }; diff --git a/test/lib/visitors/commander/commands.spec.es6 b/test/lib/visitors/commander/commands.spec.es6 index e69de29..11ab10d 100644 --- a/test/lib/visitors/commander/commands.spec.es6 +++ b/test/lib/visitors/commander/commands.spec.es6 @@ -0,0 +1,103 @@ +/** +* @module visitors.commander +* @author Patricio Ferreira <3dimentionar@gmail.com> +**/ +import helper from 'visitors/commander/commands'; +import Collection from 'util/adt/collection'; +import yargs from 'yargs'; + +describe('visitors.commander.Commands', function() { + + before(() => { + this.command = { + constructor: { + commands: Collection.new([{ + name: 'one', + aliases: ['uno'], + description: 'Description One', + options: { optionOne: {} } + }, { + name: 'two', + aliases: ['dos'], + description: 'Description Two', + options: { optionTwo: {} } + }]) + } + }; + this.sandbox = sinon.sandbox.create(); + }); + + beforeEach(() => { + this.mockYargs = this.sandbox.mock(yargs); + }); + + afterEach(() => { + this.mockYargs.verify(); + + this.sandbox.restore(); + + delete this.mockYargs; + }); + + after(() => { + delete this.command; + delete this.sandbox; + }); + + describe('commands()', () => { + + it('Should build and apply a list of commands to yargs', () => { + const { commands } = this.command.constructor; + const matcher = sinon.match((value) => { + const { command } = value; + return (_.has(command, 'name') && + _.has(command, 'aliases') && + _.has(command, 'description') && + _.has(command, 'options')); + }); + + const fake = { + _command: this.sandbox.stub(), + _aliases: this.sandbox.stub(), + _desc: this.sandbox.stub(), + _builder: this.sandbox.stub(), + _handler: this.sandbox.stub() + }; + + fake._command.withArgs(sinon.match.object) + .onFirstCall().returns(commands.get(0)) + .onSecondCall().returns(commands.get(1)); + + fake._aliases.withArgs(sinon.match.object) + .onFirstCall().returns(commands.get(0)) + .onSecondCall().returns(commands.get(1)); + + fake._desc.withArgs(sinon.match.object) + .onFirstCall().returns(commands.get(0)) + .onSecondCall().returns(commands.get(1)); + + fake._builder.withArgs(sinon.match.object) + .onFirstCall().returns(commands.get(0)) + .onSecondCall().returns(commands.get(1)); + + fake._handler.withArgs(sinon.match.object) + .onFirstCall().returns(commands.get(0)) + .onSecondCall().returns(commands.get(1)); + + const expYargsCommand = this.mockYargs.expects('command') + .twice() + .withArgs(matcher) + .returns(yargs); + + assert.equal(yargs, helper(yargs, this.command, fake)); + + assert.isTrue(fake._command.calledWith(commands.get(0))); + assert.isTrue(fake._aliases.calledWith(commands.get(0))); + assert.isTrue(fake._desc.calledWith(commands.get(0))); + assert.isTrue(fake._builder.calledWith(commands.get(0))); + assert.isTrue(fake._handler.calledWith(commands.get(0))); + }); + + }); + +}); diff --git a/test/lib/visitors/commander/epilogue.spec.es6 b/test/lib/visitors/commander/epilogue.spec.es6 index e69de29..9261d9e 100644 --- a/test/lib/visitors/commander/epilogue.spec.es6 +++ b/test/lib/visitors/commander/epilogue.spec.es6 @@ -0,0 +1,44 @@ +/** +* @module visitors.commander +* @author Patricio Ferreira <3dimentionar@gmail.com> +**/ +import epilogue from 'visitors/commander/epilogue'; +import chalk from 'chalk'; +import yargs from 'yargs'; + +describe('visitors.commander.Epilogue', function() { + + before(() => { + this.sandbox = sinon.sandbox.create(); + }); + + beforeEach(() => { + this.mockYargs = this.sandbox.mock(yargs); + }); + + afterEach(() => { + this.mockYargs.verify(); + + this.sandbox.restore(); + + delete this.mockYargs; + }); + + after(() => { + delete this.sandbox; + }); + + describe('epilogue()', () => { + + it('Should apply epilogue directive to yargs', () => { + const expEpilogue = this.mockYargs.expects('epilogue') + .once() + .withArgs(chalk.cyan('For more information, please visit http://squarebox.nahuel.io/')) + .returns(yargs); + + assert.equal(yargs, epilogue(yargs)); + }); + + }); + +}); diff --git a/test/lib/visitors/commander/parse.spec.es6 b/test/lib/visitors/commander/parse.spec.es6 index e69de29..512ecdd 100644 --- a/test/lib/visitors/commander/parse.spec.es6 +++ b/test/lib/visitors/commander/parse.spec.es6 @@ -0,0 +1,55 @@ +/** +* @module visitors.commander +* @author Patricio Ferreira <3dimentionar@gmail.com> +**/ +import parse from 'visitors/commander/parse'; +import Command from 'command'; +import yargs from 'yargs'; + +describe('visitors.commander.Parse', function() { + + before(() => { + this.command = Command.new(); + this.sandbox = sinon.sandbox.create(); + }); + + beforeEach(() => { + this.mockYargs = this.sandbox.mock(yargs); + }); + + afterEach(() => { + this.mockYargs.verify(); + + this.sandbox.restore(); + + delete this.mockYargs; + }); + + after(() => { + delete this.command; + delete this.sandbox; + }); + + describe('parse()', () => { + + it('Should call yargs parse', () => { + const input = [process.argv[0], this.cwd].concat(['sqbox', 'bundle']); + const ctx = { + _args: this.sandbox.stub().returns(input), + onParse: this.sandbox.spy() + }; + const expParse = this.mockYargs.expects('parse') + .once() + .withArgs(input, sinon.match.func) + .returns(yargs); + + assert.equal(yargs, parse(yargs, this.command, ctx)); + expParse.callArg(1); + + assert.isTrue(ctx._args.called); + assert.isTrue(ctx.onParse.called); + }); + + }); + +}); diff --git a/test/lib/visitors/commander/usage.spec.es6 b/test/lib/visitors/commander/usage.spec.es6 index e69de29..d55e1f0 100644 --- a/test/lib/visitors/commander/usage.spec.es6 +++ b/test/lib/visitors/commander/usage.spec.es6 @@ -0,0 +1,44 @@ +/** +* @module visitors.commander +* @author Patricio Ferreira <3dimentionar@gmail.com> +**/ +import usage from 'visitors/commander/usage'; +import yargs from 'yargs'; +import chalk from 'chalk'; + +describe('visitors.commander.Usage', function() { + + before(() => { + this.sandbox = sinon.sandbox.create(); + }); + + beforeEach(() => { + this.mockYargs = this.sandbox.mock(yargs); + }); + + afterEach(() => { + this.mockYargs.verify(); + + this.sandbox.restore(); + + delete this.mockYargs; + }); + + after(() => { + delete this.sandbox; + }); + + describe('usage()', () => { + + it('Should apply usage directive to yargs', () => { + const expUsage = this.mockYargs.expects('usage') + .once() + .withArgs(chalk.white('sqbox [args]')) + .returns(yargs); + + assert.equal(yargs, usage(yargs)); + }); + + }); + +}); diff --git a/test/lib/visitors/commander/version.spec.es6 b/test/lib/visitors/commander/version.spec.es6 index e69de29..b5a0e70 100644 --- a/test/lib/visitors/commander/version.spec.es6 +++ b/test/lib/visitors/commander/version.spec.es6 @@ -0,0 +1,66 @@ +/** +* @module visitors.commander +* @author Patricio Ferreira <3dimentionar@gmail.com> +**/ +import version from 'visitors/commander/version'; +import Command from 'Command'; +import yargs from 'yargs'; +import logger, { Logger } from 'util/logger/logger'; + +describe('visitors.commander.Version', function() { + + before(() => { + this.command = Command.new(); + this.sandbox = sinon.sandbox.create(); + }); + + beforeEach(() => { + this.mockYargs = this.sandbox.mock(yargs); + this.mockLogger = this.sandbox.mock(Logger.prototype); + }); + + afterEach(() => { + this.mockYargs.verify(); + this.mockLogger.verify(); + + this.sandbox.restore(); + + delete this.mockYargs; + delete this.mockLogger; + }); + + after(() => { + delete this.command; + delete this.sandbox; + }); + + describe('version()', () => { + + it('Should apply version directive to yargs', () => { + const expVersion = this.mockYargs.expects('version') + .once() + .withArgs(sinon.match(/^[0-9]\.[0-9]\.[0-9]$/)) + .returns(yargs); + + assert.equal(yargs, version(yargs, this.command)); + }); + + it('Should NOT apply version directive to yargs', () => { + this.command.dirname = './notexistent'; + const expVersion = this.mockYargs.expects('version').never(); + const expLogger = this.mockLogger.expects('_add') + .once() + .withArgs(sinon.match.string) + .returns(logger); + + const expLoggerDebug = this.mockLogger.expects('debug') + .once() + .withArgs(sinon.match.func) + .returns(logger); + + assert.equal(yargs, version(yargs, this.command)); + }); + + }); + +}); From 4c55a7fe9590d863a73b2f7c0e8593b4350456e9 Mon Sep 17 00:00:00 2001 From: kuakman <3dimentionar@gmail.com> Date: Sun, 26 Mar 2017 09:31:56 -0700 Subject: [PATCH 38/39] cli: checkpoint - Setup Unit Tests for visitors.configuration.formatter. Fixed letter casing on version.spec.es6 importing Command. --- test/lib/visitors/commander/version.spec.es6 | 2 +- .../configuration/formatter/alias.spec.es6 | 32 +++++++++++++++++++ .../configuration/formatter/exclude.spec.es6 | 32 +++++++++++++++++++ .../formatter/extensions.spec.es6 | 32 +++++++++++++++++++ .../configuration/formatter/target.spec.es6 | 32 +++++++++++++++++++ 5 files changed, 129 insertions(+), 1 deletion(-) diff --git a/test/lib/visitors/commander/version.spec.es6 b/test/lib/visitors/commander/version.spec.es6 index b5a0e70..8b4566a 100644 --- a/test/lib/visitors/commander/version.spec.es6 +++ b/test/lib/visitors/commander/version.spec.es6 @@ -3,7 +3,7 @@ * @author Patricio Ferreira <3dimentionar@gmail.com> **/ import version from 'visitors/commander/version'; -import Command from 'Command'; +import Command from 'command'; import yargs from 'yargs'; import logger, { Logger } from 'util/logger/logger'; diff --git a/test/lib/visitors/configuration/formatter/alias.spec.es6 b/test/lib/visitors/configuration/formatter/alias.spec.es6 index e69de29..de936dd 100644 --- a/test/lib/visitors/configuration/formatter/alias.spec.es6 +++ b/test/lib/visitors/configuration/formatter/alias.spec.es6 @@ -0,0 +1,32 @@ +/** +* @module visitors.configuration.formatter +* @author Patricio Ferreira <3dimentionar@gmail.com> +**/ +import alias from 'visitors/configuration/formatter/alias'; + +describe('visitors.configuration.formatter.Alias', function() { + + before(() => { + this.sandbox = sinon.sandbox.create(); + }); + + beforeEach(() => { + + }); + + afterEach(() => { + this.sandbox.restore(); + }); + + after(() => { + delete this.sandbox; + }); + + describe('alias()', () => { + + xit('Should transform parameter alias', () => {}); + xit('Should NOT transform parameter alias', () => {}); + + }); + +}); diff --git a/test/lib/visitors/configuration/formatter/exclude.spec.es6 b/test/lib/visitors/configuration/formatter/exclude.spec.es6 index e69de29..cf6210f 100644 --- a/test/lib/visitors/configuration/formatter/exclude.spec.es6 +++ b/test/lib/visitors/configuration/formatter/exclude.spec.es6 @@ -0,0 +1,32 @@ +/** +* @module visitors.configuration.formatter +* @author Patricio Ferreira <3dimentionar@gmail.com> +**/ +import exclude from 'visitors/configuration/formatter/exclude'; + +describe('visitors.configuration.formatter.Exclude', function() { + + before(() => { + this.sandbox = sinon.sandbox.create(); + }); + + beforeEach(() => { + + }); + + afterEach(() => { + this.sandbox.restore(); + }); + + after(() => { + delete this.sandbox; + }); + + describe('exclude()', () => { + + xit('Should transform parameter exclude', () => {}); + xit('Should NOT transform parameter exclude', () => {}); + + }); + +}); diff --git a/test/lib/visitors/configuration/formatter/extensions.spec.es6 b/test/lib/visitors/configuration/formatter/extensions.spec.es6 index e69de29..54fd995 100644 --- a/test/lib/visitors/configuration/formatter/extensions.spec.es6 +++ b/test/lib/visitors/configuration/formatter/extensions.spec.es6 @@ -0,0 +1,32 @@ +/** +* @module visitors.configuration.formatter +* @author Patricio Ferreira <3dimentionar@gmail.com> +**/ +import extensions from 'visitors/configuration/formatter/extensions'; + +describe('visitors.configuration.formatter.Extensions', function() { + + before(() => { + this.sandbox = sinon.sandbox.create(); + }); + + beforeEach(() => { + + }); + + afterEach(() => { + this.sandbox.restore(); + }); + + after(() => { + delete this.sandbox; + }); + + describe('extensions()', () => { + + xit('Should transform parameter extensions', () => {}); + xit('Should NOT transform parameter extensions', () => {}); + + }); + +}); diff --git a/test/lib/visitors/configuration/formatter/target.spec.es6 b/test/lib/visitors/configuration/formatter/target.spec.es6 index e69de29..71aac97 100644 --- a/test/lib/visitors/configuration/formatter/target.spec.es6 +++ b/test/lib/visitors/configuration/formatter/target.spec.es6 @@ -0,0 +1,32 @@ +/** +* @module visitors.configuration.formatter +* @author Patricio Ferreira <3dimentionar@gmail.com> +**/ +import target from 'visitors/configuration/formatter/target'; + +describe('visitors.configuration.formatter.Target', function() { + + before(() => { + this.sandbox = sinon.sandbox.create(); + }); + + beforeEach(() => { + + }); + + afterEach(() => { + this.sandbox.restore(); + }); + + after(() => { + delete this.sandbox; + }); + + describe('target()', () => { + + xit('Should transform parameter target', () => {}); + xit('Should NOT transform parameter target', () => {}); + + }); + +}); From 919284e334bf6a0716cab2b63db64c1972331796 Mon Sep 17 00:00:00 2001 From: kuakman <3dimentionar@gmail.com> Date: Sun, 26 Mar 2017 10:55:34 -0700 Subject: [PATCH 39/39] cli: checkpoint - Added unit test coverage to visitors.configuration.formatter.Alias, Exclude, Extensions and Target. --- .../configuration/formatter/alias.es6 | 1 + .../configuration/formatter/exclude.es6 | 2 +- .../configuration/formatter/extensions.es6 | 2 +- .../configuration/formatter/target.es6 | 54 ++++++++++++++++--- .../configuration/formatter/alias.spec.es6 | 33 ++++++------ .../configuration/formatter/exclude.spec.es6 | 30 +++++------ .../formatter/extensions.spec.es6 | 31 ++++++----- .../configuration/formatter/target.spec.es6 | 46 +++++++++++----- 8 files changed, 125 insertions(+), 74 deletions(-) diff --git a/lib/visitors/configuration/formatter/alias.es6 b/lib/visitors/configuration/formatter/alias.es6 index 05ca73c..14cb930 100644 --- a/lib/visitors/configuration/formatter/alias.es6 +++ b/lib/visitors/configuration/formatter/alias.es6 @@ -11,5 +11,6 @@ import _ from 'underscore'; * @return {Object} **/ export default (input = '') => { + if(input.length === 0) return {}; return _.chain(input.split(',')).invoke('split', ':').object().value(); }; diff --git a/lib/visitors/configuration/formatter/exclude.es6 b/lib/visitors/configuration/formatter/exclude.es6 index 4009dd4..6e1d260 100644 --- a/lib/visitors/configuration/formatter/exclude.es6 +++ b/lib/visitors/configuration/formatter/exclude.es6 @@ -11,5 +11,5 @@ import _ from 'underscore'; * @return {Object} **/ export default (input = '') => { - return input.split(','); + return (_.isString(input) && input.length > 0) ? input.split(',') : []; }; diff --git a/lib/visitors/configuration/formatter/extensions.es6 b/lib/visitors/configuration/formatter/extensions.es6 index 005d33a..f639ced 100644 --- a/lib/visitors/configuration/formatter/extensions.es6 +++ b/lib/visitors/configuration/formatter/extensions.es6 @@ -11,5 +11,5 @@ import _ from 'underscore'; * @return {Object} **/ export default (input = '') => { - return input.split(','); + return (_.isString(input) && input.length > 0) ? input.split(',') : []; }; diff --git a/lib/visitors/configuration/formatter/target.es6 b/lib/visitors/configuration/formatter/target.es6 index 169a7da..48044a7 100644 --- a/lib/visitors/configuration/formatter/target.es6 +++ b/lib/visitors/configuration/formatter/target.es6 @@ -4,6 +4,47 @@ **/ import _ from 'underscore'; +/** +* Supported Formats +* @const +* @private +* @type {Array} +**/ +const formats = ['ifie', 'umd', 'amd', 'cjs']; + +/** +* Validate Format +* @const +* @param {String} format - module format +* @return {Boolean} +**/ +const valid = (format) => { + return _.contains(formats, format); +}; + +/** +* Create Target +* @const +* @param {Object} m - memoized object version to export +* @return {Object} +**/ +const target = (m, v, ps) => { + m[v[0]] = { destination: ps[1], format: ps[0] }; + return m; +}; + +/** +* Create Reducer +* @const +* @param {Object} m - memoized object version to export +* @return {Object} +**/ +const create = (m, v) => { + if(!_.defined(v[1])) return m; + let ps = v[1].split(':'); + return valid(ps[0]) ? target(m, v, ps) : m; +}; + /** * Target Formatter * @static @@ -11,12 +52,9 @@ import _ from 'underscore'; * @return {Object} **/ export default (input = '') => { - return _.chain(input.split(',')) - .invoke('split', '>') - .reduce((m, v, k) => { - let ps = v[1].split(':'); - m[v[0]] = { destination: ps[1], format: ps[0] }; - return m; - }, {}) - .value(); + return (_.isString(input) && input.length > 0) ? + _.chain(input.split(',')) + .invoke('split', '>') + .reduce(create, {}) + .value() : {}; }; diff --git a/test/lib/visitors/configuration/formatter/alias.spec.es6 b/test/lib/visitors/configuration/formatter/alias.spec.es6 index de936dd..3cb2e9c 100644 --- a/test/lib/visitors/configuration/formatter/alias.spec.es6 +++ b/test/lib/visitors/configuration/formatter/alias.spec.es6 @@ -6,26 +6,23 @@ import alias from 'visitors/configuration/formatter/alias'; describe('visitors.configuration.formatter.Alias', function() { - before(() => { - this.sandbox = sinon.sandbox.create(); - }); - - beforeEach(() => { - - }); - - afterEach(() => { - this.sandbox.restore(); - }); - - after(() => { - delete this.sandbox; - }); - describe('alias()', () => { - xit('Should transform parameter alias', () => {}); - xit('Should NOT transform parameter alias', () => {}); + it('Should transform parameter alias', () => { + const input = 'one:./path/one,two:./path/two'; + const exp = alias(input); + + assert.isObject(exp); + assert.property(exp, 'one'); + assert.property(exp, 'two'); + assert.propertyVal(exp, 'one', './path/one'); + assert.propertyVal(exp, 'two', './path/two'); + }); + + it('Should NOT transform parameter alias', () => { + const exp = alias(); + assert.isTrue(_.isEmpty(exp)); + }); }); diff --git a/test/lib/visitors/configuration/formatter/exclude.spec.es6 b/test/lib/visitors/configuration/formatter/exclude.spec.es6 index cf6210f..5f2fa80 100644 --- a/test/lib/visitors/configuration/formatter/exclude.spec.es6 +++ b/test/lib/visitors/configuration/formatter/exclude.spec.es6 @@ -6,26 +6,24 @@ import exclude from 'visitors/configuration/formatter/exclude'; describe('visitors.configuration.formatter.Exclude', function() { - before(() => { - this.sandbox = sinon.sandbox.create(); - }); - - beforeEach(() => { - - }); + describe('exclude()', () => { - afterEach(() => { - this.sandbox.restore(); - }); + it('Should transform parameter exclude', () => { + const input = './path/one,./path/two'; + const exp = exclude(input); - after(() => { - delete this.sandbox; - }); + assert.isArray(exp); + assert.oneOf('./path/one', exp); + assert.oneOf('./path/two', exp); + }); - describe('exclude()', () => { + it('Should NOT transform parameter exclude', () => { + let exp = exclude(); + assert.isTrue(_.isEmpty(exp)); - xit('Should transform parameter exclude', () => {}); - xit('Should NOT transform parameter exclude', () => {}); + exp = exclude({}); + assert.isTrue(_.isEmpty(exp)); + }); }); diff --git a/test/lib/visitors/configuration/formatter/extensions.spec.es6 b/test/lib/visitors/configuration/formatter/extensions.spec.es6 index 54fd995..6290b28 100644 --- a/test/lib/visitors/configuration/formatter/extensions.spec.es6 +++ b/test/lib/visitors/configuration/formatter/extensions.spec.es6 @@ -6,26 +6,25 @@ import extensions from 'visitors/configuration/formatter/extensions'; describe('visitors.configuration.formatter.Extensions', function() { - before(() => { - this.sandbox = sinon.sandbox.create(); - }); - - beforeEach(() => { - - }); + describe('extensions()', () => { - afterEach(() => { - this.sandbox.restore(); - }); + it('Should transform parameter extensions', () => { + const input = '.js,.es6,.jsx'; + const exp = extensions(input); - after(() => { - delete this.sandbox; - }); + assert.isArray(exp); + assert.oneOf('.js', exp); + assert.oneOf('.es6', exp); + assert.oneOf('.jsx', exp); + }); - describe('extensions()', () => { + it('Should NOT transform parameter extensions', () => { + let exp = extensions(); + assert.isTrue(_.isEmpty(exp)); - xit('Should transform parameter extensions', () => {}); - xit('Should NOT transform parameter extensions', () => {}); + exp = extensions({}); + assert.isTrue(_.isEmpty(exp)); + }); }); diff --git a/test/lib/visitors/configuration/formatter/target.spec.es6 b/test/lib/visitors/configuration/formatter/target.spec.es6 index 71aac97..8de6fde 100644 --- a/test/lib/visitors/configuration/formatter/target.spec.es6 +++ b/test/lib/visitors/configuration/formatter/target.spec.es6 @@ -6,26 +6,44 @@ import target from 'visitors/configuration/formatter/target'; describe('visitors.configuration.formatter.Target', function() { - before(() => { - this.sandbox = sinon.sandbox.create(); - }); + describe('target()', () => { - beforeEach(() => { + it('Should transform parameter target', () => { + const input = 't1>umd:./dist/t1,t2>cjs:./dist/t2,t3>inv:./dist/t3'; + const exp = target(input); - }); + assert.isObject(exp); + assert.property(exp, 't1'); + assert.property(exp, 't2'); + assert.notProperty(exp, 't3'); - afterEach(() => { - this.sandbox.restore(); - }); + assert.property(exp.t1, 'destination'); + assert.property(exp.t1, 'format'); - after(() => { - delete this.sandbox; - }); + assert.property(exp.t2, 'destination'); + assert.property(exp.t2, 'format'); + }); - describe('target()', () => { + it('Should NOT transform ALL parameter targets (invalid nomenclature)', () => { + let input = 't1>umd:./dist/t1,t2:./dist/t2'; + let exp = target(input); + + assert.isObject(exp); + assert.property(exp, 't1'); + assert.notProperty(exp, 't2'); + + input = 't1,t2>cjs:./dist/t2'; + exp = target(input); + + assert.isObject(exp); + assert.property(exp, 't2'); + assert.notProperty(exp, 't1'); + }); - xit('Should transform parameter target', () => {}); - xit('Should NOT transform parameter target', () => {}); + it('Should NOT transform parameter target', () => { + const exp = target(); + assert.isTrue(_.isEmpty(exp)); + }); });