diff --git a/README.md b/README.md index 9be8aa9..54c448c 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,20 @@ expect(expanded[3][0]).to.equal('2.4.01'); - ```even``` - ```odd``` +## Custom matchers + +It is possible to add a custom *matcher callback*, a function which gets executed for each possible position within the range. + +The stack of matchers will continue to execute until a position has been accepted or until the stack ends. + +```js +expr.addMatcher(function (position, fragments) { + if (position === '1.1.45') return true; + if (fragments[0] === 1 && fragments[2] === 96) return true; + return false; +}); +``` + ## Develop - ```make test``` @@ -51,6 +65,7 @@ expect(expanded[3][0]).to.equal('2.4.01'); - CHANGED: *events* are now called *notes* [dilla/8](https://github.com/adamrenklint/dilla/issues/8) - **1.1.0** - CHANGED: expects options object instead of barsPerLoop and beatsPerBar separately [#4](https://github.com/adamrenklint/dilla-expressions/issues/4) + - NEW: possible to add custom matcher callback [#3](https://github.com/adamrenklint/dilla-expressions/issues/3) ## License diff --git a/index.js b/index.js index 740bfe0..aea8411 100644 --- a/index.js +++ b/index.js @@ -26,6 +26,13 @@ function getFragments (position) { }); } +var matchers = []; + +function addMatcher (matcher) { + if (!matcher || typeof matcher !== 'function') throw new Error('Invalid argument: matcher is not a function'); + matchers.push(matcher); +} + function makeExpressionFunction (expression) { var exprFragments = getFragments(expression); return function expressionFn (position) { @@ -36,13 +43,24 @@ function makeExpressionFunction (expression) { if (exprFragment === 'even' && positionFragments[index] % 2 === 0) return; if (exprFragment === 'odd' && positionFragments[index] % 2 === 1) return; if (exprFragment === '*') return; + // if (typeof exprFragment === 'string' && exprFragment.indexOf('%') >= 0) { // console.log('deal with modulus', exprFragment, positionFragments[index]) // } // position is invalid, break out early + // console.log('>', position, positionFragments); valid = false; return true; }); + + var _matchers = matchers.slice(); + var matcher; + while (!valid && _matchers.length) { + matcher = _matchers.shift(); + if (matcher(position, positionFragments)) { + valid = true; + } + } return valid; }; } @@ -75,4 +93,5 @@ function expressions (notes, options) { }); } +expressions.addMatcher = addMatcher; module.exports = expressions; diff --git a/test/index.test.js b/test/index.test.js index 1df7124..0de1b42 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -368,3 +368,114 @@ describe('when using mixed expression', function () { // expect(result[7][0]).to.equal('1.2.91'); // }); }); + +describe('addMatcher (matcher)', function () { + describe('when matcher is not a function', function () { + it('should throw an error', function () { + expect(function () { + expr.addMatcher(); + }).to.throw(Error); + expect(function () { + expr.addMatcher('foo'); + }).to.throw(Error); + expect(function () { + expr.addMatcher(1323); + }).to.throw(Error); + expect(function () { + expr.addMatcher({'pos': '1.1.01' }); + }).to.throw(Error); + expect(function () { + expr.addMatcher(['1.1.1.1.1']); + }).to.throw(Error); + }); + }); + + describe('when matcher is a function', function () { + it('should execute the matcher for each possible note', function () { + var count = 0; + var last = null; + expr.addMatcher(function (position, fragments) { + count++; + last = position; + }); + expr([ + ['1.1.*'] + ], standardOptions); + expect(count).to.equal(672); + expect(last).to.equal('2.4.96'); + }); + describe('when matcher returns false', function () { + describe('when another rule matches', function () { + it('should include the note', function () { + expr.addMatcher(function (position, fragments) { + if (position === '1.1.03') return false; + }); + var result = expr([ + ['1.1.odd'] + ], standardOptions); + var found = result.filter(function (res) { + return res[0] === '1.1.03'; + }); + expect(found.length).to.equal(1); + }); + }); + describe('when no other rule matches', function () { + it('should not include the note', function () { + expr.addMatcher(function (position, fragments) { + if (position === '1.1.02') return false; + }); + var result = expr([ + ['1.1.odd'] + ], standardOptions); + var found = result.filter(function (res) { + return res[0] === '1.1.02'; + }); + expect(found.length).to.equal(0); + }); + }); + }); + describe('when matcher returns true', function () { + describe('when a previous rule matches', function () { + it('should include the note', function () { + expr.addMatcher(function (position, fragments) { + if (position === '1.1.05') return true; + }); + var result = expr([ + ['1.1.odd'] + ], standardOptions); + var found = result.filter(function (res) { + return res[0] === '1.1.05'; + }); + expect(found.length).to.equal(1); + }); + it('should not execute matcher', function () { + var count = 0; + expr.addMatcher(function (position, fragments) { + return true; + }); + expr.addMatcher(function (position, fragments) { + count++; + }); + var result = expr([ + ['1.1.*'] + ], standardOptions); + expect(count).to.equal(0); + }); + }); + describe('when no other rule matches', function () { + it('should include the note', function () { + expr.addMatcher(function (position, fragments) { + if (position === '1.1.04') return true; + }); + var result = expr([ + ['1.1.odd'] + ], standardOptions); + var found = result.filter(function (res) { + return res[0] === '1.1.04'; + }); + expect(found.length).to.equal(1); + }); + }); + }); + }); +});