react 0.1.2 → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.npmignore +2 -1
- package/README.md +48 -8
- package/examples/default-events1.js +52 -0
- package/examples/default1.js +41 -0
- package/lib/core.js +1 -0
- package/lib/dsl.js +122 -0
- package/lib/error.js +17 -7
- package/lib/parse.js +1 -0
- package/lib/task.js +7 -2
- package/lib/validate.js +49 -1
- package/package.json +1 -1
- package/react.js +4 -4
- package/test/chain.test.js +6 -5
- package/test/core.test.js +5 -4
- package/test/dsl.test.js +265 -0
- package/test/fstr.test.js +6 -6
- package/test/module-use.test.js +64 -18
- package/test/pcode.test.js +4 -4
- package/test/validate.test.js +49 -1
package/.npmignore
CHANGED
package/README.md
CHANGED
|
@@ -25,13 +25,13 @@ It takes inspiration from several projects including:
|
|
|
25
25
|
|
|
26
26
|
## Goals
|
|
27
27
|
|
|
28
|
+
- Minimize boilerplate code needed for working with asynchronous functions
|
|
29
|
+
- Minimize the need to customize your code simply to use async flow control. The use of a flow control module ideally should not affect the way you write your code, it should only help take over some of the burden.
|
|
28
30
|
- Improved error and exception handling
|
|
29
31
|
- Provide useful stack traces and context information for easier debugging
|
|
30
|
-
- Minimize boilerplate code needed for working with asynchronous functions
|
|
31
32
|
- Make code more readable and easier to understand which should translate to less defects
|
|
32
33
|
- Provide the right level of abstraction to make it easier to refactor code, without being too magical
|
|
33
34
|
- Allow the mixing of pure functions, method calls, and callback style functions in the flow
|
|
34
|
-
- Minimize the need to customize your code simply to use async flow control. The use of a flow control module ideally should not affect the way you write your code, it should only help take over some of the burden.
|
|
35
35
|
|
|
36
36
|
## Supports
|
|
37
37
|
|
|
@@ -61,9 +61,9 @@ To reduce the boilerplate code needed and improve error handling, React automati
|
|
|
61
61
|
|
|
62
62
|
## Design
|
|
63
63
|
|
|
64
|
-
- Parse and validate
|
|
64
|
+
- Parse and validate DSL rules at module load time
|
|
65
65
|
- Validate the flow AST at module load time - determine if dependencies can all be met as defined
|
|
66
|
-
- Execute the flow AST by calling the function with
|
|
66
|
+
- Execute the flow AST by calling the function with arguments
|
|
67
67
|
|
|
68
68
|
## Installing
|
|
69
69
|
|
|
@@ -75,8 +75,8 @@ Pull from github - http://github.com/jeffbski/react
|
|
|
75
75
|
|
|
76
76
|
## Examples
|
|
77
77
|
|
|
78
|
-
1. [
|
|
79
|
-
2. [
|
|
78
|
+
1. [Default DSL](#defaultDSL)
|
|
79
|
+
2. [Direct AST](#directAST)
|
|
80
80
|
3. [Using pseudocode DSL](#pcode)
|
|
81
81
|
4. [Using jquery-like chaining DSL](#chain)
|
|
82
82
|
|
|
@@ -85,6 +85,44 @@ These live in the examples folder so they are ready to run.
|
|
|
85
85
|
Also see test/module-use.test.js for more examples as well
|
|
86
86
|
as the specific tests for the DSL you want to use.
|
|
87
87
|
|
|
88
|
+
<a name="defaultDSL"/>
|
|
89
|
+
### Example using default DSL
|
|
90
|
+
|
|
91
|
+
```javascript
|
|
92
|
+
// in your foo module
|
|
93
|
+
var react = require('react');
|
|
94
|
+
|
|
95
|
+
// some normal async and sync functions
|
|
96
|
+
function loadUser(uid, cb){ }
|
|
97
|
+
function loadFile(filename, cb){ }
|
|
98
|
+
function markdown(filedata) { }
|
|
99
|
+
function writeOutput(html, user, cb){ }
|
|
100
|
+
function loadEmailTemplate(cb) { }
|
|
101
|
+
function customizeEmail(user, emailHtml, cb) { }
|
|
102
|
+
function deliverEmail(custEmailHtml, cb) { }
|
|
103
|
+
|
|
104
|
+
// define fn, glue together with react, it will parallelize
|
|
105
|
+
// starts with name and in/out params, then the tasks
|
|
106
|
+
var loadAndSend = react('loadAndSend', 'uid, filename, cb -> err, user',
|
|
107
|
+
loadUser, 'uid, cb -> err, user',
|
|
108
|
+
loadFile, 'filename, cb -> err, filemd',
|
|
109
|
+
markdown, 'filemd -> html', // no cb, implies sync fn
|
|
110
|
+
writeOutput, 'html, user, cb -> err, htmlBytesWritten',
|
|
111
|
+
loadEmailTemplate, 'cb -> err, emailmd',
|
|
112
|
+
markdown, 'emailmd -> emailHtml', // no cb, implies sync fn
|
|
113
|
+
customizeEmail, 'user, emailHtml, cb -> err, custEHtml',
|
|
114
|
+
deliverEmail, 'custEHtml, cb -> err, custBytesWritten'
|
|
115
|
+
);
|
|
116
|
+
exports.loadAndSend = loadAndSend; // is a normal fn created by react
|
|
117
|
+
|
|
118
|
+
// in a different module far far away, use this as any other node function
|
|
119
|
+
var foo = require('foo');
|
|
120
|
+
foo.loadAndSend(100, 'bar.md', function (err, user) {
|
|
121
|
+
// tasks were parallelized based on their depedencies
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
|
|
88
126
|
<a name="directAST"/>
|
|
89
127
|
### Example directly using AST
|
|
90
128
|
|
|
@@ -204,6 +242,7 @@ fn(2, 3, function (err, m, s) {
|
|
|
204
242
|
|
|
205
243
|
## Status
|
|
206
244
|
|
|
245
|
+
- 2012-01-10 - Create default DSL for react(), create error for missing variables, list remaining tasks when flow won't complete
|
|
207
246
|
- 2011-12-21 - Refactor from ground up with tests, changes to the interfaces
|
|
208
247
|
- 2011-10-26 - React is in active development and interface may change frequently in these early stages. Current code is functional but does not perform validation yet. Additional interfaces are planned to make it easy to define flows in a variety of ways. Documentation and examples forthcoming.
|
|
209
248
|
|
|
@@ -214,12 +253,13 @@ ok ast.test.js .................... 10/10
|
|
|
214
253
|
ok cb-task.test.js ................ 31/31
|
|
215
254
|
ok chain.test.js .................. 56/56
|
|
216
255
|
ok core.test.js ................... 98/98
|
|
256
|
+
ok dsl.test.js .................... 58/58
|
|
217
257
|
ok event-manager.test.js .......... 13/13
|
|
218
258
|
ok exec-options.test.js ............. 3/3
|
|
219
259
|
ok finalcb-task.test.js ............. 5/5
|
|
220
260
|
ok fstr.test.js ................... 64/64
|
|
221
261
|
ok input-parser.test.js ........... 15/15
|
|
222
|
-
ok module-use.test.js .............
|
|
262
|
+
ok module-use.test.js ............. 64/64
|
|
223
263
|
ok pcode.test.js .................. 65/65
|
|
224
264
|
ok ret-task.test.js ............... 31/31
|
|
225
265
|
ok task.test.js ..................... 1/1
|
|
@@ -227,7 +267,7 @@ ok validate-cb-task.test.js ......... 6/6
|
|
|
227
267
|
ok validate-ret-task.test.js ........ 7/7
|
|
228
268
|
ok validate.test.js ............... 26/26
|
|
229
269
|
ok vcon.test.js ................... 42/42
|
|
230
|
-
total ...........................
|
|
270
|
+
total ........................... 613/613
|
|
231
271
|
|
|
232
272
|
ok
|
|
233
273
|
```
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/*jshint white: false */
|
|
3
|
+
|
|
4
|
+
var react = require('../'); // require('react');
|
|
5
|
+
|
|
6
|
+
//output events as tasks start and complete
|
|
7
|
+
react.events.on('*', function (obj) {
|
|
8
|
+
var time = new Date();
|
|
9
|
+
time.setTime(obj.time);
|
|
10
|
+
var eventTimeStr = time.toISOString();
|
|
11
|
+
var argsNoCb = obj.args.filter(function (a) { return (typeof(a) !== 'function'); });
|
|
12
|
+
if (obj.event === 'task.complete') {
|
|
13
|
+
console.error('%s: %s \tmsecs:(%s) \n\targs:(%s) \n\tresults:(%s)\n',
|
|
14
|
+
obj.event, obj.name, obj.elapsedTime, argsNoCb, obj.results);
|
|
15
|
+
} else {
|
|
16
|
+
console.error('%s: %s \n\targs:(%s)\n', obj.event, obj.name, argsNoCb);
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
function loadUser(uid, cb){ setTimeout(cb, 100, null, "User"+uid); }
|
|
22
|
+
function loadFile(filename, cb){ setTimeout(cb, 100, null, 'Filedata'+filename); }
|
|
23
|
+
function markdown(filedata) { return 'html'+filedata; }
|
|
24
|
+
function prepareDirectory(outDirname, cb){ setTimeout(cb, 200, null, 'dircreated-'+outDirname); }
|
|
25
|
+
function writeOutput(html, user, cb){ setTimeout(cb, 300, null, html+'_bytesWritten'); }
|
|
26
|
+
function loadEmailTemplate(cb) { setTimeout(cb, 50, null, 'emailmd'); }
|
|
27
|
+
function customizeEmail(user, emailHtml) { return 'cust-'+user+emailHtml; }
|
|
28
|
+
function deliverEmail(custEmailHtml, cb) { setTimeout(cb, 100, null, 'delivered-'+custEmailHtml); }
|
|
29
|
+
|
|
30
|
+
function useHtml(err, html, user, bytesWritten) {
|
|
31
|
+
if (err) {
|
|
32
|
+
console.log('***Error: %s', err);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
console.log('final result: %s, user: %s, written:%s', html, user, bytesWritten);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
var loadAndSave = react('loadAndSave', 'filename, uid, outDirname, cb -> err, html, user, bytesWritten', // name, in/out params
|
|
39
|
+
loadUser, 'uid, cb -> err, user', // calling async fn loadUser with uid, callback is called with err and user
|
|
40
|
+
loadFile, 'filename, cb -> err, filedata',
|
|
41
|
+
markdown, 'filedata -> html', // using a sync function
|
|
42
|
+
prepareDirectory, 'outDirname, cb -> err, dircreated',
|
|
43
|
+
writeOutput, 'html, user, cb -> err, bytesWritten', { after: prepareDirectory }, // only after prepareDirectory done
|
|
44
|
+
loadEmailTemplate, 'cb -> err, emailmd',
|
|
45
|
+
markdown, 'emailmd -> emailHtml', // using a sync function
|
|
46
|
+
customizeEmail, 'user, emailHtml -> err, custEmailHtml', // sync fn
|
|
47
|
+
deliverEmail, 'custEmailHtml, cb -> err, deliveredEmail', { after: writeOutput } // only after writeOutput is done
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
loadAndSave('file.md', 100, '/tmp/foo', useHtml); // executing the flow
|
|
51
|
+
|
|
52
|
+
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/*jshint white: false */
|
|
3
|
+
|
|
4
|
+
var react = require('../'); // require('react');
|
|
5
|
+
|
|
6
|
+
function loadUser(uid, cb){ setTimeout(cb, 100, null, "User"+uid); }
|
|
7
|
+
function loadFile(filename, cb){ setTimeout(cb, 100, null, 'Filedata'+filename); }
|
|
8
|
+
function markdown(filedata) { return 'html'+filedata; }
|
|
9
|
+
function prepareDirectory(outDirname, cb){ setTimeout(cb, 200, null, 'dircreated-'+outDirname); }
|
|
10
|
+
function writeOutput(html, user, cb){ setTimeout(cb, 300, null, html+'_bytesWritten'); }
|
|
11
|
+
function loadEmailTemplate(cb) { setTimeout(cb, 50, null, 'emailmd'); }
|
|
12
|
+
function customizeEmail(user, emailHtml) { return 'cust-'+user+emailHtml; }
|
|
13
|
+
function deliverEmail(custEmailHtml, cb) { setTimeout(cb, 100, null, 'delivered-'+custEmailHtml); }
|
|
14
|
+
|
|
15
|
+
function useHtml(err, html, user, bytesWritten) {
|
|
16
|
+
if (err) {
|
|
17
|
+
console.log('***Error: %s', err);
|
|
18
|
+
console.error(err.stack);
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
console.log('final result: %s, user: %s, written:%s', html, user, bytesWritten);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// define fn, glue together with react, it will parallelize
|
|
25
|
+
// starts with name and in/out params, then the tasks
|
|
26
|
+
var loadAndSave = react('loadAndSave', 'filename, uid, outDir, cb -> err, html, user, bytes',
|
|
27
|
+
loadUser, 'uid, cb -> err, user', // calling async loadUser with uid, cb called with err and user
|
|
28
|
+
loadFile, 'filename, cb -> err, filedata',
|
|
29
|
+
markdown, 'filedata -> html', // using a sync function
|
|
30
|
+
prepareDirectory, 'outDir, cb -> err, dircreated',
|
|
31
|
+
writeOutput, 'html, user, cb -> err, bytes', { after: prepareDirectory }, // only after prepareDirectory done
|
|
32
|
+
loadEmailTemplate, 'cb -> err, emailmd',
|
|
33
|
+
markdown, 'emailmd -> emailHtml', // using a sync function
|
|
34
|
+
customizeEmail, 'user, emailHtml -> custEmailHtml', // sync function
|
|
35
|
+
deliverEmail, 'custEmailHtml, cb -> err, deliveredEmail', { after: writeOutput } // only after writeOutput is done
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
//in another module you can use this as any other exported fn
|
|
39
|
+
loadAndSave('file.md', 100, '/tmp/foo', useHtml); // executing the flow
|
|
40
|
+
|
|
41
|
+
|
package/lib/core.js
CHANGED
package/lib/dsl.js
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/*jshint regexp: false */
|
|
3
|
+
|
|
4
|
+
var sprintf = require('sprintf').sprintf;
|
|
5
|
+
var core = require('./core.js');
|
|
6
|
+
var parse = require('./parse.js');
|
|
7
|
+
var tutil = require('./task.js');
|
|
8
|
+
|
|
9
|
+
var INOUT_PARAMS_NO_MATCH = 'params in wrong format, wanted "foo, bar cb -> err, baz" - found: %s';
|
|
10
|
+
var EXTRA_TASKARG = 'extra unmatched task arg: %s';
|
|
11
|
+
|
|
12
|
+
var CB_NAMES_RE = /^cb|callback$/i; //cb, Cb, CB, callback, Callback
|
|
13
|
+
var ERR_NAMES_RE = /^err$/i; // err, ERR, Err, ...
|
|
14
|
+
|
|
15
|
+
function filterOutTrailingCbParam(args) { // if has trailing cb | callback param, filter it out
|
|
16
|
+
if (args.length && args[args.length - 1].match(CB_NAMES_RE)) args.pop();
|
|
17
|
+
return args;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function filterOutLeadingErrParam(args) { // if leading err param, filter it out
|
|
21
|
+
if (args.length && args[0].match(ERR_NAMES_RE)) args.shift();
|
|
22
|
+
return args;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
var inOutDefParse = {
|
|
26
|
+
regex: /^([^\-]*)(->\s*(.*))?$/i,
|
|
27
|
+
fn: function (m) {
|
|
28
|
+
var inParams = parse.splitTrimFilterArgs(m[1]);
|
|
29
|
+
var lastParam = inParams[inParams.length - 1];
|
|
30
|
+
var type = (lastParam && CB_NAMES_RE.test(lastParam)) ? 'cb' : 'ret';
|
|
31
|
+
var outParams = parse.splitTrimFilterArgs(m[3]);
|
|
32
|
+
return {
|
|
33
|
+
type: type,
|
|
34
|
+
inDef: filterOutTrailingCbParam(inParams),
|
|
35
|
+
outDef: filterOutLeadingErrParam(outParams)
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
function parseInOutParams(str) {
|
|
41
|
+
var objDef = parse.parseStr(str, [inOutDefParse], INOUT_PARAMS_NO_MATCH);
|
|
42
|
+
objDef.inDef = filterOutTrailingCbParam(objDef.inDef);
|
|
43
|
+
return objDef;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function parseTasks(arr) {
|
|
47
|
+
var tasks = [];
|
|
48
|
+
var fn, obj, result;
|
|
49
|
+
while (arr.length >= 2) {
|
|
50
|
+
obj = {};
|
|
51
|
+
fn = arr.shift();
|
|
52
|
+
result = parseInOutParams(arr.shift());
|
|
53
|
+
if (typeof(arr[0]) === 'object') obj = arr.shift(); // has options, use as obj
|
|
54
|
+
obj.f = fn;
|
|
55
|
+
obj.a = result.inDef;
|
|
56
|
+
var type = result.type;
|
|
57
|
+
obj.out = result.outDef;
|
|
58
|
+
obj.type = type;
|
|
59
|
+
tasks.push(obj);
|
|
60
|
+
}
|
|
61
|
+
if (arr.length) throw new Error(sprintf(EXTRA_TASKARG, arr[0]));
|
|
62
|
+
return tasks;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
Parse the variable arguments into in/out params, options, tasks
|
|
67
|
+
*/
|
|
68
|
+
function parseVargs(vargs) {
|
|
69
|
+
var inOutParamStr = vargs.shift() || '';
|
|
70
|
+
// if next arg is object, shift it off as options
|
|
71
|
+
var options = (vargs.length && typeof(vargs[0]) === 'object') ? vargs.shift() : { };
|
|
72
|
+
var taskDefArr = vargs; // rest are for the tasks
|
|
73
|
+
var defObj = {
|
|
74
|
+
inOutParamStr: inOutParamStr,
|
|
75
|
+
taskDefArr: taskDefArr,
|
|
76
|
+
options: options
|
|
77
|
+
};
|
|
78
|
+
return defObj;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
function dslDefine(name, arg1, arg2, argN) {
|
|
83
|
+
var reactFn = core();
|
|
84
|
+
var defObj = parseVargs(Array.prototype.slice.call(arguments, 1)); // name, already used
|
|
85
|
+
var inOutDef = parseInOutParams(defObj.inOutParamStr);
|
|
86
|
+
var ast = {
|
|
87
|
+
name: name,
|
|
88
|
+
inParams: inOutDef.inDef,
|
|
89
|
+
tasks: parseTasks(defObj.taskDefArr),
|
|
90
|
+
outTask: { a: inOutDef.outDef }
|
|
91
|
+
};
|
|
92
|
+
if (defObj.options) Object.keys(defObj.options).forEach(function (k) { ast[k] = defObj.options[k]; });
|
|
93
|
+
var errors = reactFn.setAndValidateAST(ast);
|
|
94
|
+
if (errors.length) {
|
|
95
|
+
var errorStr = errors.join('\n');
|
|
96
|
+
throw new Error(errorStr);
|
|
97
|
+
}
|
|
98
|
+
return reactFn;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function selectFirst(name, arg1, arg2, argN) {
|
|
102
|
+
var reactFn = core();
|
|
103
|
+
var defObj = parseVargs(Array.prototype.slice.call(arguments, 1)); // name, already used
|
|
104
|
+
var inOutDef = parseInOutParams(defObj.inOutParamStr);
|
|
105
|
+
var tasks = tutil.serializeTasks(parseTasks(defObj.taskDefArr));
|
|
106
|
+
var ast = {
|
|
107
|
+
name: name,
|
|
108
|
+
inParams: inOutDef.inDef,
|
|
109
|
+
tasks: tasks,
|
|
110
|
+
outTask: { type: 'finalcbFirst', a: inOutDef.outDef },
|
|
111
|
+
};
|
|
112
|
+
if (defObj.options) Object.keys(defObj.options).forEach(function (k) { ast[k] = defObj.options[k]; });
|
|
113
|
+
var errors = reactFn.setAndValidateAST(ast);
|
|
114
|
+
if (errors.length) {
|
|
115
|
+
var errorStr = errors.join('\n');
|
|
116
|
+
throw new Error(errorStr);
|
|
117
|
+
}
|
|
118
|
+
return reactFn;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
module.exports = dslDefine;
|
|
122
|
+
module.exports.selectFirst = selectFirst;
|
package/lib/error.js
CHANGED
|
@@ -9,19 +9,29 @@ function ensureStackTraceLimitSet(stackTraceLimit) {
|
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
function fName(fn) {
|
|
12
|
-
|
|
12
|
+
if (!fn) return 'undefined';
|
|
13
|
+
return (fn && fn.name) ? fn.name : fn;
|
|
13
14
|
}
|
|
14
15
|
|
|
15
16
|
function formatErrorMeta(err) {
|
|
16
17
|
if (!err.meta) return;
|
|
17
18
|
var vcon = err.meta.vcon;
|
|
18
19
|
var task = err.meta.task;
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
20
|
+
var errString = '\n\n';
|
|
21
|
+
if (task && task.f && task.a) {
|
|
22
|
+
errString += ('Error occurs in Task function: ' + fName(task.f) + '(' + task.a.join(',') + ')\n\n');
|
|
23
|
+
}
|
|
24
|
+
if (vcon) {
|
|
25
|
+
errString += 'Variable Context: \n';
|
|
26
|
+
errString += util.inspect(vcon);
|
|
27
|
+
errString += '\n\n';
|
|
28
|
+
}
|
|
29
|
+
if (task && task.f) {
|
|
30
|
+
errString += 'Task Source:\n\n';
|
|
31
|
+
errString += task.f.toString(); //TODO need to pretty print function, gets collapsed
|
|
32
|
+
errString += '\n\n';
|
|
33
|
+
}
|
|
34
|
+
return errString;
|
|
25
35
|
}
|
|
26
36
|
|
|
27
37
|
function augmentError(err, meta) {
|
package/lib/parse.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
var sprintf = require('sprintf').sprintf;
|
|
4
4
|
|
|
5
5
|
function splitTrimFilterArgs(commaSepArgs) { //parse 'one, two' into ['one', 'two']
|
|
6
|
+
if (!commaSepArgs) return [];
|
|
6
7
|
return commaSepArgs.split(',') //split on commas
|
|
7
8
|
.map(function (s) { return s.trim(); }) //trim
|
|
8
9
|
.filter(function (s) { return (s); }); //filter out empty strings
|
package/lib/task.js
CHANGED
|
@@ -25,7 +25,7 @@ function outTaskTypeKeys() { return Object.keys(OUT_TASK_TYPES); }
|
|
|
25
25
|
|
|
26
26
|
var LOCAL_FN_MISSING = 'function: %s not found in locals or input params - task[%s]';
|
|
27
27
|
var TASKDEF_IS_OBJECT = 'task must be an object';
|
|
28
|
-
var NO_TASKS_RUNNING_WONT_COMPLETE = 'no tasks running, flow will not complete';
|
|
28
|
+
var NO_TASKS_RUNNING_WONT_COMPLETE = 'no tasks running, flow will not complete, remaining tasks: %s';
|
|
29
29
|
var TASK_TYPE_SHOULD_MATCH = 'task.type should match one of ' +
|
|
30
30
|
Object.keys(TASK_TYPES).join(', ');
|
|
31
31
|
|
|
@@ -201,7 +201,12 @@ function checkIfTasksRunning(vCon, tasks, handleError) {
|
|
|
201
201
|
var tasksRunning = tasks.filter(function (t) {
|
|
202
202
|
return (t.status === STATUS.RUNNING || t.status === STATUS.READY);
|
|
203
203
|
});
|
|
204
|
-
if (!tasksRunning.length)
|
|
204
|
+
if (!tasksRunning.length) {
|
|
205
|
+
var remainingTasks = tasks.filter(function (t) { return (!t.status); });
|
|
206
|
+
var remainingTNames = remainingTasks.map(function (t) { return t.name; });
|
|
207
|
+
var errMsg = sprintf(NO_TASKS_RUNNING_WONT_COMPLETE, remainingTNames.join(', '));
|
|
208
|
+
handleError({}, new Error(errMsg));
|
|
209
|
+
}
|
|
205
210
|
}
|
|
206
211
|
|
|
207
212
|
function findReadyAndExec(vCon, tasks, tasksByName, handleError, contExec) {
|
package/lib/validate.js
CHANGED
|
@@ -11,14 +11,19 @@ var TASKS_ARR = 'ast.tasks must be an array of tasks';
|
|
|
11
11
|
var NAMES_UNIQUE = 'ast.tasks that specify name need to be unique, duplicate:';
|
|
12
12
|
var LOCALS_NOTNULL = 'ast.locals should not be null';
|
|
13
13
|
var DUP_OUTPUTS = 'multiple tasks output the same param, must be unique. param';
|
|
14
|
+
var MISSING_INPUTS = 'missing or mispelled variable referenced in flow definition: %s';
|
|
14
15
|
|
|
15
16
|
var validateInParams, validateTasks, validateOutTask, validateTaskNamesUnique;
|
|
16
|
-
var validateLocals, validateOuputsUnique;
|
|
17
|
+
var validateLocals, validateOuputsUnique, validateNoMissingNames;
|
|
17
18
|
|
|
18
19
|
function format_error(errmsg, obj) {
|
|
19
20
|
return sprintf('%s - %s', errmsg, util.inspect(obj));
|
|
20
21
|
}
|
|
21
22
|
|
|
23
|
+
function isProp(str) { // true if is a property name (contains a dot)
|
|
24
|
+
return (str.indexOf('.') !== -1);
|
|
25
|
+
}
|
|
26
|
+
|
|
22
27
|
/**
|
|
23
28
|
validate the AST return Errors
|
|
24
29
|
@example
|
|
@@ -37,6 +42,7 @@ function validate(ast) {
|
|
|
37
42
|
if (errors.length === 0) { // if no errors do additional validation
|
|
38
43
|
if (ast.outTask.type !== 'finalcbFirst') errors = errors.concat(validateOuputsUnique(ast.tasks));
|
|
39
44
|
errors = errors.concat(tutil.validateLocalFunctions(ast.inParams, ast.tasks, ast.locals));
|
|
45
|
+
errors = errors.concat(validateNoMissingNames(ast));
|
|
40
46
|
}
|
|
41
47
|
return errors;
|
|
42
48
|
}
|
|
@@ -99,4 +105,46 @@ function validateOuputsUnique(taskDefs) {
|
|
|
99
105
|
return errors;
|
|
100
106
|
}
|
|
101
107
|
|
|
108
|
+
/**
|
|
109
|
+
validate there are no missing or mispelled param names in any task inputs
|
|
110
|
+
or the final task output
|
|
111
|
+
|
|
112
|
+
@return array of errors, or empty array if none
|
|
113
|
+
*/
|
|
114
|
+
function validateNoMissingNames(ast) {
|
|
115
|
+
var errors = [];
|
|
116
|
+
var names = {};
|
|
117
|
+
if (ast.locals) {
|
|
118
|
+
names = Object.keys(ast.locals).reduce(function (accum, k) { // start with locals
|
|
119
|
+
accum[k] = true;
|
|
120
|
+
return accum;
|
|
121
|
+
}, names);
|
|
122
|
+
}
|
|
123
|
+
ast.inParams.reduce(function (accum, p) { // add input params
|
|
124
|
+
accum[p] = true;
|
|
125
|
+
return accum;
|
|
126
|
+
}, names);
|
|
127
|
+
ast.tasks.reduce(function (accum, t) { // add task outputs
|
|
128
|
+
return t.out.reduce(function (innerAccum, p) {
|
|
129
|
+
innerAccum[p] = true;
|
|
130
|
+
return innerAccum;
|
|
131
|
+
}, accum);
|
|
132
|
+
}, names);
|
|
133
|
+
|
|
134
|
+
// now we have all possible provided vars, check task inputs are accounted for
|
|
135
|
+
ast.tasks.reduce(function (accum, t) { // for all tasks
|
|
136
|
+
return t.a.reduce(function (innerAccum, p) { // for all in params, except property
|
|
137
|
+
if (!isProp(p) && !names[p]) innerAccum.push(sprintf(MISSING_INPUTS, p)); // add error if missing
|
|
138
|
+
return innerAccum;
|
|
139
|
+
}, accum);
|
|
140
|
+
}, errors);
|
|
141
|
+
|
|
142
|
+
// now check the final task outputs
|
|
143
|
+
ast.outTask.a.reduce(function (accum, p) { // for final task out params
|
|
144
|
+
if (!isProp(p) && !names[p]) accum.push(sprintf(MISSING_INPUTS, p)); // add error if missing
|
|
145
|
+
return accum;
|
|
146
|
+
}, errors);
|
|
147
|
+
return errors;
|
|
148
|
+
}
|
|
149
|
+
|
|
102
150
|
module.exports = validate;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react",
|
|
3
3
|
"description": "React is a javascript module to make it easier to work with asynchronous code, by reducing boilerplate code and improving error and exception handling while allowing variable and task dependencies when defining flow.",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.2.3",
|
|
5
5
|
"author": "Jeff Barczewski <jeff.barczewski@gmail.com>",
|
|
6
6
|
"repository": { "type": "git", "url": "http://github.com/jeffbski/react.git" },
|
|
7
7
|
"bugs" : { "url": "http://github.com/jeffbski/react/issues" },
|
package/react.js
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
var core = require('./lib/core.js');
|
|
4
4
|
|
|
5
|
-
module.exports = core
|
|
6
|
-
module.exports.options = core.options;
|
|
7
|
-
module.exports.events = core.events; //
|
|
5
|
+
module.exports = require('./lib/dsl.js'); // core + default dsl
|
|
6
|
+
module.exports.options = core.options; // global react options
|
|
7
|
+
module.exports.events = core.events; // global react event emitter
|
|
8
8
|
|
|
9
|
-
// interfaces
|
|
9
|
+
// additional interfaces
|
|
10
10
|
module.exports.fstrDefine = require('./lib/fstr.js');
|
|
11
11
|
module.exports.pcodeDefine = require('./lib/pcode.js');
|
|
12
12
|
module.exports.chainDefine = require('./lib/chain.js');
|
package/test/chain.test.js
CHANGED
|
@@ -63,10 +63,11 @@ test('in triple first param -> inParams["foo", "bar", "baz"]', function (t) {
|
|
|
63
63
|
|
|
64
64
|
test('single task, single out params', function (t) {
|
|
65
65
|
var fn = chainDefine()
|
|
66
|
+
.in('a', 'b')
|
|
66
67
|
.out('c')
|
|
67
68
|
.async(falpha).in('a', 'b').out('c')
|
|
68
69
|
.end();
|
|
69
|
-
t.deepEqual(fn.ast.inParams, []);
|
|
70
|
+
t.deepEqual(fn.ast.inParams, ['a', 'b']);
|
|
70
71
|
t.deepEqual(fn.ast.tasks, [
|
|
71
72
|
{ f: falpha, type: 'cb', a: ['a', 'b'], out: ['c'], name: 'falpha' }
|
|
72
73
|
]);
|
|
@@ -77,11 +78,11 @@ test('single task, single out params', function (t) {
|
|
|
77
78
|
test('single task, err and out params', function (t) {
|
|
78
79
|
var fn = chainDefine()
|
|
79
80
|
.out('err', 'c')
|
|
80
|
-
.async(falpha).in(
|
|
81
|
+
.async(falpha).in().out('err', 'c')
|
|
81
82
|
.end();
|
|
82
83
|
t.deepEqual(fn.ast.inParams, []);
|
|
83
84
|
t.deepEqual(fn.ast.tasks, [
|
|
84
|
-
{ f: falpha, type: 'cb', a: [
|
|
85
|
+
{ f: falpha, type: 'cb', a: [], out: ['c'], name: 'falpha' }
|
|
85
86
|
]);
|
|
86
87
|
t.deepEqual(fn.ast.outTask, { a: ['c'], type: 'finalcb' });
|
|
87
88
|
t.end();
|
|
@@ -90,11 +91,11 @@ test('single task, err and out params', function (t) {
|
|
|
90
91
|
test('single task, ERR and out params', function (t) {
|
|
91
92
|
var fn = chainDefine()
|
|
92
93
|
.out('ERR', 'c')
|
|
93
|
-
.async(falpha).in(
|
|
94
|
+
.async(falpha).in().out('ERR', 'c')
|
|
94
95
|
.end();
|
|
95
96
|
t.deepEqual(fn.ast.inParams, []);
|
|
96
97
|
t.deepEqual(fn.ast.tasks, [
|
|
97
|
-
{ f: falpha, type: 'cb', a: [
|
|
98
|
+
{ f: falpha, type: 'cb', a: [], out: ['c'], name: 'falpha' }
|
|
98
99
|
]);
|
|
99
100
|
t.deepEqual(fn.ast.outTask, { a: ['c'], type: 'finalcb' });
|
|
100
101
|
t.end();
|
package/test/core.test.js
CHANGED
|
@@ -198,14 +198,15 @@ test('error when cant complete', function (t) {
|
|
|
198
198
|
inParams: ['a', 'b', 'c'],
|
|
199
199
|
tasks: [
|
|
200
200
|
{ f: multiply, a: ['a', 'b'], out: ['c.mult'] },
|
|
201
|
-
{ f: fnRetsSum, a: ['c.bad', 'b'], out: ['c.sum'], type: 'ret' }
|
|
201
|
+
{ f: fnRetsSum, a: ['c.bad', 'b'], out: ['c.sum'], type: 'ret' },
|
|
202
|
+
{ f: add, a: ['c.sum', 'a'], out: ['d']}
|
|
202
203
|
],
|
|
203
|
-
outTask: { a: ['c.mult', 'c.sum', '
|
|
204
|
+
outTask: { a: ['c.mult', 'c.sum', 'd'] }
|
|
204
205
|
});
|
|
205
206
|
t.deepEqual(errors, [], 'no validation errors');
|
|
206
207
|
|
|
207
|
-
fn(2, 3, { foo: 1 }, function (err, cmult, csum,
|
|
208
|
-
t.equal(err.message, 'no tasks running, flow will not complete');
|
|
208
|
+
fn(2, 3, { foo: 1 }, function (err, cmult, csum, d) {
|
|
209
|
+
t.equal(err.message, 'no tasks running, flow will not complete, remaining tasks: fnRetsSum, add');
|
|
209
210
|
t.end();
|
|
210
211
|
});
|
|
211
212
|
});
|
package/test/dsl.test.js
ADDED
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var test = require('tap').test;
|
|
4
|
+
var sprintf = require('sprintf').sprintf;
|
|
5
|
+
|
|
6
|
+
var react = require('../'); // require('react');
|
|
7
|
+
|
|
8
|
+
function falpha() { }
|
|
9
|
+
function fbeta() { }
|
|
10
|
+
|
|
11
|
+
test('module exports is a fn with properties', function (t) {
|
|
12
|
+
t.type(react, 'function', 'has define by DSL method'); //
|
|
13
|
+
t.type(react.selectFirst, 'function', 'has selectFirst define method');
|
|
14
|
+
t.end();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test('no arguments -> empty name, inParams, tasks, outTask', function (t) {
|
|
18
|
+
var r = react();
|
|
19
|
+
t.equal(r.ast.name, undefined);
|
|
20
|
+
t.deepEqual(r.ast.inParams, []);
|
|
21
|
+
t.deepEqual(r.ast.tasks, []);
|
|
22
|
+
t.deepEqual(r.ast.outTask, { a: [], type: 'finalcb' });
|
|
23
|
+
t.end();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
test('empty first string -> empty name, inParams, tasks, outTask', function (t) {
|
|
28
|
+
var r = react('');
|
|
29
|
+
t.equal(r.ast.name, '');
|
|
30
|
+
t.deepEqual(r.ast.inParams, []);
|
|
31
|
+
t.deepEqual(r.ast.tasks, []);
|
|
32
|
+
t.deepEqual(r.ast.outTask, { a: [], type: 'finalcb' });
|
|
33
|
+
t.end();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
test('single first string -> name, inParams["foo"], empty tasks, outTask', function (t) {
|
|
38
|
+
var r = react('foo');
|
|
39
|
+
t.equal(r.ast.name, 'foo');
|
|
40
|
+
t.deepEqual(r.ast.inParams, []);
|
|
41
|
+
t.deepEqual(r.ast.tasks, []);
|
|
42
|
+
t.deepEqual(r.ast.outTask, { a: [], type: 'finalcb' });
|
|
43
|
+
t.end();
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test('triple first string -> inParams["foo", "bar", "baz"], empty tasks, outTask',
|
|
47
|
+
function (t) {
|
|
48
|
+
var r = react('myName', ' foo, bar,baz ');
|
|
49
|
+
t.equal(r.ast.name, 'myName');
|
|
50
|
+
t.deepEqual(r.ast.inParams, ['foo', 'bar', 'baz']);
|
|
51
|
+
t.deepEqual(r.ast.tasks, []);
|
|
52
|
+
t.deepEqual(r.ast.outTask, { a: [], type: 'finalcb' });
|
|
53
|
+
t.end();
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test('single task, single out params', function (t) {
|
|
57
|
+
var r = react('myName', 'cb -> err, c',
|
|
58
|
+
falpha, 'cb -> err, c'
|
|
59
|
+
);
|
|
60
|
+
t.deepEqual(r.ast.inParams, []);
|
|
61
|
+
t.deepEqual(r.ast.tasks, [
|
|
62
|
+
{ f: falpha, a: [], out: ['c'], type: 'cb', name: 'falpha'}
|
|
63
|
+
]);
|
|
64
|
+
t.deepEqual(r.ast.outTask, { a: ['c'], type: 'finalcb' });
|
|
65
|
+
t.end();
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test('single task, err and out params', function (t) {
|
|
69
|
+
var r = react('myName', 'cb -> err, c',
|
|
70
|
+
falpha, 'cb -> err, c'
|
|
71
|
+
);
|
|
72
|
+
t.deepEqual(r.ast.inParams, []);
|
|
73
|
+
t.deepEqual(r.ast.tasks, [
|
|
74
|
+
{ f: falpha, a: [], out: ['c'], type: 'cb', name: 'falpha'}
|
|
75
|
+
]);
|
|
76
|
+
t.deepEqual(r.ast.outTask, { a: ['c'], type: 'finalcb' });
|
|
77
|
+
t.end();
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test('single task, ERR and out params', function (t) {
|
|
81
|
+
var r = react('myName', 'cb -> ERR, c',
|
|
82
|
+
falpha, 'cb -> ERR, c'
|
|
83
|
+
);
|
|
84
|
+
t.deepEqual(r.ast.inParams, []);
|
|
85
|
+
t.deepEqual(r.ast.tasks, [
|
|
86
|
+
{ f: falpha, a: [], out: ['c'], type: 'cb', name: 'falpha'}
|
|
87
|
+
]);
|
|
88
|
+
t.deepEqual(r.ast.outTask, { a: ['c'], type: 'finalcb' });
|
|
89
|
+
t.end();
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test('cb used in defs is simply ignored', function (t) {
|
|
93
|
+
var r = react('myName', 'a, b, cb -> err, c',
|
|
94
|
+
falpha, 'a, b, cb -> err, c'
|
|
95
|
+
);
|
|
96
|
+
t.deepEqual(r.ast.inParams, ['a', 'b']);
|
|
97
|
+
t.deepEqual(r.ast.tasks, [
|
|
98
|
+
{ f: falpha, a: ['a', 'b'], out: ['c'], type: 'cb', name: 'falpha'}
|
|
99
|
+
]);
|
|
100
|
+
t.deepEqual(r.ast.outTask, { a: ['c'], type: 'finalcb' });
|
|
101
|
+
t.end();
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test('callback used in defs is simply ignored', function (t) {
|
|
105
|
+
var r = react('myName', 'a, b, callback -> err, c',
|
|
106
|
+
falpha, 'a, b, callback -> err, c'
|
|
107
|
+
);
|
|
108
|
+
t.deepEqual(r.ast.inParams, ['a', 'b']);
|
|
109
|
+
t.deepEqual(r.ast.tasks, [
|
|
110
|
+
{ f: falpha, a: ['a', 'b'], out: ['c'], type: 'cb', name: 'falpha'}
|
|
111
|
+
]);
|
|
112
|
+
t.deepEqual(r.ast.outTask, { a: ['c'], type: 'finalcb' });
|
|
113
|
+
t.end();
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test('two inputs, two tasks, two out params', function (t) {
|
|
117
|
+
var r = react('myName', 'a, b, cb -> err, c, d',
|
|
118
|
+
falpha, 'a, b, cb -> err, c',
|
|
119
|
+
fbeta, 'a, b, cb -> err, d, e'
|
|
120
|
+
);
|
|
121
|
+
t.deepEqual(r.ast.inParams, ['a', 'b']);
|
|
122
|
+
t.deepEqual(r.ast.tasks, [
|
|
123
|
+
{ f: falpha, a: ['a', 'b'], out: ['c'], type: 'cb', name: 'falpha'},
|
|
124
|
+
{ f: fbeta, a: ['a', 'b'], out: ['d', 'e'], type: 'cb', name: 'fbeta'}
|
|
125
|
+
]);
|
|
126
|
+
t.deepEqual(r.ast.outTask, { a: ['c', 'd'], type: 'finalcb' });
|
|
127
|
+
t.end();
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test('two inputs, two tasks, two out params, options', function (t) {
|
|
131
|
+
var r = react('myName', 'a, b, cb -> err, c, d',
|
|
132
|
+
{ otherOptFoo: 'foo'}, // main flow options
|
|
133
|
+
falpha, 'a, b, cb -> err, c',
|
|
134
|
+
fbeta, 'a, b, cb -> err, d, e'
|
|
135
|
+
);
|
|
136
|
+
t.deepEqual(r.ast.inParams, ['a', 'b']);
|
|
137
|
+
t.deepEqual(r.ast.tasks, [
|
|
138
|
+
{ f: falpha, a: ['a', 'b'], out: ['c'], type: 'cb', name: 'falpha'},
|
|
139
|
+
{ f: fbeta, a: ['a', 'b'], out: ['d', 'e'], type: 'cb', name: 'fbeta'}
|
|
140
|
+
]);
|
|
141
|
+
t.deepEqual(r.ast.outTask, { a: ['c', 'd'], type: 'finalcb' });
|
|
142
|
+
t.equal(r.ast.name, 'myName', 'name should match');
|
|
143
|
+
t.equal(r.ast.otherOptFoo, 'foo', 'other options should pass through');
|
|
144
|
+
t.end();
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test('two inputs, two mixed tasks, two out params', function (t) {
|
|
148
|
+
var r = react('myName', 'a, b, cb -> err, c, d',
|
|
149
|
+
falpha, 'a, b, cb -> err, c',
|
|
150
|
+
fbeta, 'a, b -> d'
|
|
151
|
+
);
|
|
152
|
+
t.deepEqual(r.ast.inParams, ['a', 'b']);
|
|
153
|
+
t.deepEqual(r.ast.tasks, [
|
|
154
|
+
{ f: falpha, a: ['a', 'b'], out: ['c'], type: 'cb', name: 'falpha'},
|
|
155
|
+
{ f: fbeta, a: ['a', 'b'], out: ['d'], type: 'ret', name: 'fbeta'}
|
|
156
|
+
]);
|
|
157
|
+
t.deepEqual(r.ast.outTask, { a: ['c', 'd'], type: 'finalcb' });
|
|
158
|
+
t.end();
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
test('two inputs, two mixed tasks, two out params, opts', function (t) {
|
|
163
|
+
var r = react('myName', 'a, b, cb -> err, c, d',
|
|
164
|
+
falpha, 'a, cb -> err, c', { after: fbeta },
|
|
165
|
+
fbeta, 'a, b -> d'
|
|
166
|
+
);
|
|
167
|
+
t.deepEqual(r.ast.inParams, ['a', 'b']);
|
|
168
|
+
t.deepEqual(r.ast.tasks, [
|
|
169
|
+
{ after: ['fbeta'], f: falpha, a: ['a'], out: ['c'], type: 'cb', name: 'falpha'},
|
|
170
|
+
{ f: fbeta, a: ['a', 'b'], out: ['d'], type: 'ret', name: 'fbeta'}
|
|
171
|
+
]);
|
|
172
|
+
t.deepEqual(r.ast.outTask, { a: ['c', 'd'], type: 'finalcb' });
|
|
173
|
+
t.end();
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
// Object use
|
|
178
|
+
test('object prop task params', function (t) {
|
|
179
|
+
var r = react('myName', 'a, b, cb -> err, c, e',
|
|
180
|
+
falpha, 'a, b.cat, cb -> err, c',
|
|
181
|
+
fbeta, 'c.dog, b -> d',
|
|
182
|
+
'd.egg', 'c, cb -> err, e'
|
|
183
|
+
);
|
|
184
|
+
t.deepEqual(r.ast.inParams, ['a', 'b']);
|
|
185
|
+
t.deepEqual(r.ast.tasks, [
|
|
186
|
+
{ f: falpha, a: ['a', 'b.cat'], out: ['c'], type: 'cb', name: 'falpha'},
|
|
187
|
+
{ f: fbeta, a: ['c.dog', 'b'], out: ['d'], type: 'ret', name: 'fbeta'},
|
|
188
|
+
{ f: 'd.egg', a: ['c'], out: ['e'], type: 'cb', name: 'd.egg'}
|
|
189
|
+
]);
|
|
190
|
+
t.deepEqual(r.ast.outTask, { a: ['c', 'e'], type: 'finalcb' });
|
|
191
|
+
t.end();
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
test('extra arg throws error', function (t) {
|
|
195
|
+
var fn = function () {
|
|
196
|
+
var r = react('myName', 'a, b, cb -> err, c, d',
|
|
197
|
+
falpha, 'a -> err, c', { after: fbeta },
|
|
198
|
+
fbeta, 'a, b -> returns d',
|
|
199
|
+
'extraBadArg'
|
|
200
|
+
);
|
|
201
|
+
};
|
|
202
|
+
t.throws(fn, new Error('extra unmatched task arg: extraBadArg'));
|
|
203
|
+
t.end();
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
test('not enough args throws error', function (t) {
|
|
207
|
+
var fn = function () {
|
|
208
|
+
var r = react('myName', 'a, b, cb -> c, d',
|
|
209
|
+
falpha, 'a -> err, c', { after: fbeta },
|
|
210
|
+
fbeta
|
|
211
|
+
);
|
|
212
|
+
};
|
|
213
|
+
t.throws(fn, new Error(sprintf('extra unmatched task arg: %s', fbeta)));
|
|
214
|
+
t.end();
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
test('long example', function (t) {
|
|
218
|
+
t.plan(4);
|
|
219
|
+
function loadUser(uid, cb){ setTimeout(cb, 100, null, "User"+uid); }
|
|
220
|
+
function loadFile(filename, cb){ setTimeout(cb, 100, null, 'Filedata'+filename); }
|
|
221
|
+
function markdown(filedata) { return 'html'+filedata; }
|
|
222
|
+
function prepareDirectory(outDirname, cb){ setTimeout(cb, 200, null, 'dircreated-'+outDirname); }
|
|
223
|
+
function writeOutput(html, user, cb){ setTimeout(cb, 300, null, html+'_bytesWritten'); }
|
|
224
|
+
function loadEmailTemplate(cb) { setTimeout(cb, 50, null, 'emailmd'); }
|
|
225
|
+
function customizeEmail(user, emailHtml) { return 'cust-'+user+emailHtml; }
|
|
226
|
+
function deliverEmail(custEmailHtml, cb) { setTimeout(cb, 100, null, 'delivered-'+custEmailHtml); }
|
|
227
|
+
var loadAndSave = react('loadAndSave', 'filename, uid, outDirname, cb -> err, html, user, bytesWritten', // name, in/out params
|
|
228
|
+
loadUser, 'uid, cb -> err, user', // calling async fn loadUser with uid, callback is called with err and user
|
|
229
|
+
loadFile, 'filename, cb -> err, filedata',
|
|
230
|
+
markdown, 'filedata -> html', // using a sync function
|
|
231
|
+
prepareDirectory, 'outDirname, cb -> err, dircreated',
|
|
232
|
+
writeOutput, 'html, user, cb -> err, bytesWritten', { after: prepareDirectory }, // only after prepareDirectory done
|
|
233
|
+
loadEmailTemplate, 'cb -> err, emailmd',
|
|
234
|
+
markdown, 'emailmd -> emailHtml', // using a sync function
|
|
235
|
+
customizeEmail, 'user, emailHtml -> custEmailHtml',
|
|
236
|
+
deliverEmail, 'custEmailHtml, cb -> err, deliveredEmail', { after: writeOutput } // only after writeOutput is done
|
|
237
|
+
);
|
|
238
|
+
loadAndSave('file.md', 100, '/tmp/foo', function (err, html, user, bytesWritten) { // executing the flow
|
|
239
|
+
t.equal(err, null);
|
|
240
|
+
t.equal(html, 'htmlFiledatafile.md');
|
|
241
|
+
t.equal(user, 'User100');
|
|
242
|
+
t.equal(bytesWritten, 'htmlFiledatafile.md_bytesWritten');
|
|
243
|
+
t.end();
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
// selectFirst
|
|
248
|
+
|
|
249
|
+
test('selectFirst', function (t) {
|
|
250
|
+
var r = react.selectFirst('myName', 'a, b, cb -> c',
|
|
251
|
+
{ otherOptFoo: 'foo'}, // main options
|
|
252
|
+
falpha, 'a, b, cb -> err, c',
|
|
253
|
+
fbeta, 'a, b -> c'
|
|
254
|
+
);
|
|
255
|
+
t.equal(r.ast.name, 'myName');
|
|
256
|
+
t.deepEqual(r.ast.inParams, ['a', 'b']);
|
|
257
|
+
t.deepEqual(r.ast.tasks, [
|
|
258
|
+
{ f: falpha, a: ['a', 'b'], out: ['c'], type: 'cb', name: 'falpha'},
|
|
259
|
+
{ f: fbeta, a: ['a', 'b'], out: ['c'], type: 'ret', name: 'fbeta', after: ['falpha']}
|
|
260
|
+
]);
|
|
261
|
+
t.deepEqual(r.ast.outTask, { type: 'finalcbFirst', a: ['c'] });
|
|
262
|
+
t.equal(r.ast.name, 'myName', 'name should match if supplied');
|
|
263
|
+
t.equal(r.ast.otherOptFoo, 'foo', 'other options should pass through');
|
|
264
|
+
t.end();
|
|
265
|
+
});
|
package/test/fstr.test.js
CHANGED
|
@@ -48,10 +48,10 @@ test('triple first string -> inParams["foo", "bar", "baz"], empty tasks, outTask
|
|
|
48
48
|
});
|
|
49
49
|
|
|
50
50
|
test('single task, single out params', function (t) {
|
|
51
|
-
var r = fstrDefine('', [
|
|
51
|
+
var r = fstrDefine('a, b', [
|
|
52
52
|
falpha, 'a, b -> err, c'
|
|
53
53
|
], 'c');
|
|
54
|
-
t.deepEqual(r.ast.inParams, []);
|
|
54
|
+
t.deepEqual(r.ast.inParams, ['a', 'b']);
|
|
55
55
|
t.deepEqual(r.ast.tasks, [
|
|
56
56
|
{ f: falpha, a: ['a', 'b'], out: ['c'], type: 'cb', name: 'falpha'}
|
|
57
57
|
]);
|
|
@@ -60,10 +60,10 @@ test('single task, single out params', function (t) {
|
|
|
60
60
|
});
|
|
61
61
|
|
|
62
62
|
test('single task, err and out params', function (t) {
|
|
63
|
-
var r = fstrDefine('', [
|
|
63
|
+
var r = fstrDefine('a, b', [
|
|
64
64
|
falpha, 'a, b -> err, c'
|
|
65
65
|
], 'err, c');
|
|
66
|
-
t.deepEqual(r.ast.inParams, []);
|
|
66
|
+
t.deepEqual(r.ast.inParams, ['a', 'b']);
|
|
67
67
|
t.deepEqual(r.ast.tasks, [
|
|
68
68
|
{ f: falpha, a: ['a', 'b'], out: ['c'], type: 'cb', name: 'falpha'}
|
|
69
69
|
]);
|
|
@@ -72,10 +72,10 @@ test('single task, err and out params', function (t) {
|
|
|
72
72
|
});
|
|
73
73
|
|
|
74
74
|
test('single task, ERR and out params', function (t) {
|
|
75
|
-
var r = fstrDefine('', [
|
|
75
|
+
var r = fstrDefine('a, b', [
|
|
76
76
|
falpha, 'a, b -> ERR, c'
|
|
77
77
|
], 'ERR, c');
|
|
78
|
-
t.deepEqual(r.ast.inParams, []);
|
|
78
|
+
t.deepEqual(r.ast.inParams, ['a', 'b']);
|
|
79
79
|
t.deepEqual(r.ast.tasks, [
|
|
80
80
|
{ f: falpha, a: ['a', 'b'], out: ['c'], type: 'cb', name: 'falpha'}
|
|
81
81
|
]);
|
package/test/module-use.test.js
CHANGED
|
@@ -9,13 +9,16 @@ var react = require('../react');
|
|
|
9
9
|
@example
|
|
10
10
|
var react = require('react');
|
|
11
11
|
react.options.an_option = 'something';
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
12
|
+
|
|
13
|
+
// define function
|
|
14
|
+
var loadAndSave = react('myName', 'one, two, cb -> err, result1, result2',
|
|
15
|
+
foo, 'one, cb -> err, cat',
|
|
16
|
+
bar, 'two, cat, cb -> err, dog',
|
|
17
|
+
baz, 'dog, cb -> err, result1',
|
|
18
|
+
bum, 'dog, cb -> err, result2');
|
|
19
|
+
|
|
20
|
+
// OR using AST
|
|
21
|
+
|
|
19
22
|
var loadAndSave = react();
|
|
20
23
|
loadAndSave.setAndValidateAST({
|
|
21
24
|
inParams: ['one', 'two'],
|
|
@@ -26,11 +29,11 @@ var react = require('../react');
|
|
|
26
29
|
//if you want to listen to task completion events
|
|
27
30
|
loadAndSave.events.on('task.complete', function (taskObj) { });
|
|
28
31
|
|
|
29
|
-
loadAndSave(1,2,cb);
|
|
32
|
+
loadAndSave(1,2,cb); // execute like any other function
|
|
30
33
|
*/
|
|
31
34
|
|
|
32
35
|
test('module exports an function object with properties', function (t) {
|
|
33
|
-
t.type(react, 'function', 'is a core constructor function');
|
|
36
|
+
t.type(react, 'function', 'is a core constructor and default dsl function');
|
|
34
37
|
t.type(react.options, 'object', 'has property for global react options');
|
|
35
38
|
t.type(react.fstrDefine, 'function', 'has fn property for using fstr dsl');
|
|
36
39
|
t.type(react.pcodeDefine, 'function', 'has fn property for using pcode dsl');
|
|
@@ -47,15 +50,7 @@ test('calling react constructor function creates new function with ast', functio
|
|
|
47
50
|
t.type(r.ast, 'object', 'is object for inspecting AST');
|
|
48
51
|
t.deepEqual(r.ast.inParams, [], 'ast.inParams should return empty array');
|
|
49
52
|
t.deepEqual(r.ast.tasks, [], 'ast.tasks() should return empty array');
|
|
50
|
-
t.deepEqual(r.ast.outTask, {
|
|
51
|
-
t.end();
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
test('deprecated react API should throw to help debugging', function (t) {
|
|
55
|
-
function badUse() {
|
|
56
|
-
var r = react('filename, uid, outDirname, cb'); //this should throw
|
|
57
|
-
}
|
|
58
|
-
t.throws(badUse, new Error('react() takes no args, check API'));
|
|
53
|
+
t.deepEqual(r.ast.outTask, { a: [], type: 'finalcb' });
|
|
59
54
|
t.end();
|
|
60
55
|
});
|
|
61
56
|
|
|
@@ -78,6 +73,57 @@ test('setAndValidateAST sets the ast and validates returning errors', function (
|
|
|
78
73
|
t.end();
|
|
79
74
|
});
|
|
80
75
|
|
|
76
|
+
test('use react() default DSL from module', function (t) {
|
|
77
|
+
t.plan(3);
|
|
78
|
+
function multiply(a, b, cb) { cb(null, a * b); }
|
|
79
|
+
function add(a, b, cb) { cb(null, a + b); }
|
|
80
|
+
var fn = react('multiplyAdd', 'a, b, cb -> err, m, s',
|
|
81
|
+
multiply, 'a, b, cb -> err, m',
|
|
82
|
+
add, 'm, a, cb -> err, s'
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
fn(2, 3, function (err, m, s) {
|
|
86
|
+
t.deepEqual(err, null, 'should not be any error');
|
|
87
|
+
t.equal(m, 6);
|
|
88
|
+
t.equal(s, 8);
|
|
89
|
+
t.end();
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test('use react.selectFirst() default DSL with events', function (t) {
|
|
94
|
+
t.plan(7);
|
|
95
|
+
function noSuccess(a, b, cb) {
|
|
96
|
+
setTimeout(function () { cb(null); }, 100); // returns undefined result
|
|
97
|
+
}
|
|
98
|
+
function noSuccessNull(a, b, cb) { cb(null, null); } // returns null result
|
|
99
|
+
function add(a, b, cb) { cb(null, a + b); }
|
|
100
|
+
|
|
101
|
+
var events = [];
|
|
102
|
+
function accumEvents(task) {
|
|
103
|
+
events.push(task);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
var fn = react.selectFirst('mySelectFirst', 'a, b, cb -> err, c',
|
|
107
|
+
noSuccess, 'a, b, cb -> err, c',
|
|
108
|
+
noSuccessNull, 'a, b, cb -> err, c',
|
|
109
|
+
add, 'a, b, cb -> err, c',
|
|
110
|
+
noSuccess, 'a, b, cb -> err, c'
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
fn.events.on('task.complete', accumEvents);
|
|
114
|
+
|
|
115
|
+
fn(2, 3, function (err, c) {
|
|
116
|
+
t.deepEqual(err, null, 'should not be any error');
|
|
117
|
+
t.equal(c, 5);
|
|
118
|
+
t.equal(events.length, 3, 'should have seen two task compl events');
|
|
119
|
+
t.equal(events[0].name, 'noSuccess', 'name matches');
|
|
120
|
+
t.equal(events[1].name, 'noSuccessNull', 'name matches');
|
|
121
|
+
t.equal(events[2].name, 'add', 'name matches');
|
|
122
|
+
t.deepEqual(events[2].results, [5], 'results match');
|
|
123
|
+
t.end();
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
|
|
81
127
|
|
|
82
128
|
test('use pcodeDefine from module', function (t) {
|
|
83
129
|
t.plan(3);
|
package/test/pcode.test.js
CHANGED
|
@@ -50,11 +50,11 @@ test('triple first string -> inParams["foo", "bar", "baz"], empty tasks, outTask
|
|
|
50
50
|
|
|
51
51
|
test('single task, single out params', function (t) {
|
|
52
52
|
var locals = { falpha: falpha };
|
|
53
|
-
var r = pcode('', [
|
|
53
|
+
var r = pcode('a, b', [
|
|
54
54
|
'c := falpha(a, b)',
|
|
55
55
|
'cb(err, c)'
|
|
56
56
|
], locals);
|
|
57
|
-
t.deepEqual(r.ast.inParams, []);
|
|
57
|
+
t.deepEqual(r.ast.inParams, ['a', 'b']);
|
|
58
58
|
t.deepEqual(r.ast.tasks, [
|
|
59
59
|
{ f: 'falpha', a: ['a', 'b'], out: ['c'], type: 'cb', name: 'falpha'}
|
|
60
60
|
]);
|
|
@@ -64,11 +64,11 @@ test('single task, single out params', function (t) {
|
|
|
64
64
|
|
|
65
65
|
test('single task, err and out params', function (t) {
|
|
66
66
|
var locals = { falpha: falpha };
|
|
67
|
-
var r = pcode('', [
|
|
67
|
+
var r = pcode('a, b', [
|
|
68
68
|
'c := falpha(a, b)',
|
|
69
69
|
'cb(err, c)'
|
|
70
70
|
], locals);
|
|
71
|
-
t.deepEqual(r.ast.inParams, []);
|
|
71
|
+
t.deepEqual(r.ast.inParams, ['a', 'b']);
|
|
72
72
|
t.deepEqual(r.ast.tasks, [
|
|
73
73
|
{ f: 'falpha', a: ['a', 'b'], out: ['c'], type: 'cb', name: 'falpha'}
|
|
74
74
|
]);
|
package/test/validate.test.js
CHANGED
|
@@ -210,9 +210,57 @@ test('multiple tasks output the same param, must be unique', function (t) {
|
|
|
210
210
|
{ f: foo, a: [], out: ['baz', 'c'] },
|
|
211
211
|
{ f: bar, a: [], out: ['c'] }
|
|
212
212
|
],
|
|
213
|
-
outTask: { a: ['
|
|
213
|
+
outTask: { a: ['baz'] }
|
|
214
214
|
};
|
|
215
215
|
var msg = 'multiple tasks output the same param, must be unique. param: c';
|
|
216
216
|
t.deepEqual(validate(ast), [msg]);
|
|
217
217
|
t.end();
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
test('missing or mispelled input variable', function (t) {
|
|
221
|
+
var ast = {
|
|
222
|
+
inParams: [],
|
|
223
|
+
tasks: [
|
|
224
|
+
{ f: foo, a: [], out: [] },
|
|
225
|
+
{ f: bar, a: ['abc'], out: [] }
|
|
226
|
+
],
|
|
227
|
+
outTask: { a: [] }
|
|
228
|
+
};
|
|
229
|
+
var msg = 'missing or mispelled variable referenced in flow definition: abc';
|
|
230
|
+
t.deepEqual(validate(ast), [msg]);
|
|
231
|
+
t.end();
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
test('missing or mispelled input variables', function (t) {
|
|
235
|
+
var ast = {
|
|
236
|
+
inParams: ['aaa', 'bbb'],
|
|
237
|
+
tasks: [
|
|
238
|
+
{ f: foo, a: ['aaa', 'cat'], out: ['ccc'] },
|
|
239
|
+
{ f: bar, a: ['abc', 'bbb', 'ccc'], out: [] }
|
|
240
|
+
],
|
|
241
|
+
outTask: { a: [] }
|
|
242
|
+
};
|
|
243
|
+
var messages = [
|
|
244
|
+
'missing or mispelled variable referenced in flow definition: cat',
|
|
245
|
+
'missing or mispelled variable referenced in flow definition: abc'
|
|
246
|
+
];
|
|
247
|
+
t.deepEqual(validate(ast), messages);
|
|
248
|
+
t.end();
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
test('missing or mispelled final output variables', function (t) {
|
|
252
|
+
var ast = {
|
|
253
|
+
inParams: ['aaa'],
|
|
254
|
+
tasks: [
|
|
255
|
+
{ f: foo, a: ['aaa'], out: ['bbb'] },
|
|
256
|
+
{ f: bar, a: ['bbb'], out: ['ccc'] }
|
|
257
|
+
],
|
|
258
|
+
outTask: { a: ['ccc', 'ddd', 'eee'] }
|
|
259
|
+
};
|
|
260
|
+
var messages = [
|
|
261
|
+
'missing or mispelled variable referenced in flow definition: ddd',
|
|
262
|
+
'missing or mispelled variable referenced in flow definition: eee'
|
|
263
|
+
];
|
|
264
|
+
t.deepEqual(validate(ast), messages);
|
|
265
|
+
t.end();
|
|
218
266
|
});
|