Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
TakenPilot committed Sep 2, 2016
0 parents commit b7e0b80
Show file tree
Hide file tree
Showing 6 changed files with 405 additions and 0 deletions.
75 changes: 75 additions & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
{
"env": {
"node": true,
"mocha": true,
"es6": true
},
"globals": {
"expect": false,
"chai": false,
"sinon": false
},
"parserOptions": {
"ecmaFeatures": {
"arrowFunctions": true,
"blockBindings": true,
"forOf": true,
"objectLiteralDuplicateProperties": true,
"objectLiteralShorthandProperties": true,
"objectLiteralShorthandMethods": true,
"octalLiterals": true,
"binaryLiterals": true,
"templateStrings": true,
"generators": true
}
},
"rules": {
"no-extra-parens": [1, "functions"],
"valid-jsdoc": [1, {
"requireReturn": false,
"requireParamDescription": false,
"requireReturnDescription": false
}],
"complexity": [2, 8],
"default-case": 2,
"guard-for-in": 2,
"no-alert": 1,
"no-floating-decimal": 1,
"no-self-compare": 2,
"no-throw-literal": 2,
"no-var": 2,
"no-void": 2,
"no-multiple-empty-lines": 2,
"quote-props": [2, "as-needed"],
"vars-on-top": 2,
"wrap-iife": 2,
"strict": [2, "global"],
"no-unused-vars": 2,
"handle-callback-err": [2, "^.*(e|E)rr"],
"no-mixed-requires": 0,
"no-new-require": 2,
"no-path-concat": 2,
"brace-style": [2, "1tbs", { "allowSingleLine": true }],
"comma-style": [2, "last"],
"comma-spacing": 2,
"indent": [2, 2, { "SwitchCase": 1 }],
"max-nested-callbacks": [2, 5],
"newline-after-var": [2, "always"],
"no-nested-ternary": 2,
"no-spaced-func": 0,
"no-trailing-spaces": 2,
"no-underscore-dangle": 0,
"no-unneeded-ternary": 1,
"one-var": 2,
"quotes": [2, "single", "avoid-escape"],
"semi": [2, "always"],
"keyword-spacing": 2,
"space-before-blocks": [2, "always"],
"space-before-function-paren": [2, {"anonymous": "always", "named": "never"}],
"space-infix-ops": [1, {"int32Hint": false}],
"spaced-comment": [2, "always"],
"generator-star-spacing": [2, "before"],
"max-depth": [2, 4],
"max-params": [2, 4]
}
}
13 changes: 13 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
.idea
.DS_Store
cache
*.pyc
*.log
BETA.md
.vagrant
*.swp
npm-debug.log*

app
dist
node_modules
92 changes: 92 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
'use strict';

const _ = require('lodash');

/**
* @param {object} rule
* @param {Array} args
* @returns {boolean}
*/
function checkWhen(rule, args) {
const when = rule.when;

switch (typeof when) {
case 'function': return !!when.apply(null, args);
case 'boolean': return when;
default: return args[0] === when;
}
}

/**
* @param {object} rule
* @param {Array} args
* @returns {*}
*/
function returnThen(rule, args) {
const then = rule.then;

switch (typeof then) {
case 'function': return then.apply(null, args);
default: return then;
}
}

/**
* Select rules whose `when` returns truthy
* @param {[{when: function, then: function}]} ruleSet
* @returns {[{when: function, then: function}]} new RuleSet
*/
function select(ruleSet) {
const selected = [];

for (let i = 0; i < ruleSet.length; i++) {
const rule = ruleSet[i],
args = _.slice(arguments, 1);

if (checkWhen(rule, args)) {
selected.push(rule);
}
}

return selected;
}

/**
* Execute the `then` of the first rule whose `when` returns truthy
* @param {[{when: function, then: function}]} ruleSet
* @returns {*} Result of first good rule
*/
function first(ruleSet) {
for (let i = 0; i < ruleSet.length; i++) {
const rule = ruleSet[i],
args = _.slice(arguments, 1);

if (checkWhen(rule, args)) {
return returnThen(rule, args);
}
}
}

/**
* Return the results of all that pass
* @param {Array} ruleSet
* @returns {Array}
*/
function all(ruleSet) {
let result = [];

for (let i = 0; i < ruleSet.length; i++) {
const rule = ruleSet[i],
args = _.slice(arguments, 1);

if (checkWhen(rule, args)) {
result.push(returnThen(rule, args));
}
}

return result;
}

module.exports.select = select;
module.exports.first = first;
module.exports.all = all;
23 changes: 23 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"name": "rulejs",
"version": "0.0.1",
"description": "rules engine; chain-of-command implementation",
"main": "index.js",
"scripts": {
"test": "mocha"
},
"keywords": [
"rules",
"rules-engine",
"chain-of-command",
"when-then"
],
"author": "Dane Stuckel",
"license": "ISC",
"devDependencies": {
"chai": "^3.5.0",
"eslint": "^3.4.0",
"lodash": "^4.15.0",
"mocha": "^3.0.2"
}
}
97 changes: 97 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
Rules.js
--------

After re-writing this pattern over three times in three different projects, I should just make it a library instead.

Works great when combined with function libraries like lodash.

## API

### First

Runs first rule where `when` returns a truthy value

### Select

Returns all rule objects where `when` returns a truthy value

### All

Returns all results where `when` returns a truthy value

## Examples

Constants:
```js
const ruleSet = [
{ when: 'a', then: 'b' },
{ when: 1, then: 2 }
]

rules.first(ruleSet, 1); // returns 2
rules.first(ruleSet, 'a'); // returns 'b'
rules.first(ruleSet, '?'); // returns undefined
```

Functions:
```js
const ruleSet = [
{ when: str => str.length > 3, then: str => str.substr(3).trim() },
{ when: true, then: str => str.toLowerCase() }
]

rules.first(ruleSet, '>>> value'); // returns 'value';
rules.first(ruleSet, 'B'); // returns 'b';
```

Multiple facts / parameters:
```js
const ruleSet = [
{
when: function (platform, cmd) {
return platform === 'win32' && cmd === 'python';
},
then: function (platform, cmd) {
return cmd += '.exe'
}
},
{
when: function (platform, ) {
return platform === 'win32' && cmd === 'ls';
},
then: function (platform, cmd) {
return 'dir';
}
}
]

rules.first(ruleSet, 'win32', 'python'); // returns 'python.exe';
rules.first(ruleSet, 'win32', 'ls'); // returns 'dir';
```

Select all that apply:
```js
const ruleSet = [
{ when: str => str.startsWith('fizz'), a: 'b', c: 'd' },
{ when: str => str.endsWith('buzz'), value: 7 },
{ when: str => str === 'fizzbuzz', width: 8, height: 9 }
]

rules.select(ruleSet, 'fizz'); // returns first item;
rules.select(ruleSet, 'buzz'); // returns second item;
rules.select(ruleSet, 'fizzbuzz'); // returns all three;
```

Get all the rule results that are truthy for `when`:

```js
const ruleSet = [
{ when: facts => facts.isLoggedIn, then: facts => fetch('http://example.com/user/' + facts.userId) },
{ when: facts => facts.isAdmin, then: facts => fetch('http://example.com/rights/' + facts.rightsId) },
{ when: facts => facts.browser === 'IE', then: facts => fetch('http://iesupport.com') }
]

const promises = rules.all(ruleSet, {isLoggedIn: true, browser: 'IE'});

return Promises.all(promises); // resolves all the API calls.
```
Loading

0 comments on commit b7e0b80

Please sign in to comment.