Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Release 0.0.1

  • Loading branch information...
commit 2dfbc85a143cb1a1d9162d88de0b080e5039269a 1 parent c49e4aa
Eldar Gabdullin authored
15 .gitignore
View
@@ -1,14 +1 @@
-lib-cov
-*.seed
-*.log
-*.csv
-*.dat
-*.out
-*.pid
-*.gz
-
-pids
-logs
-results
-
-npm-debug.log
+node_modules/
1  .npmignore
View
@@ -0,0 +1 @@
+test/
2  README.md
View
@@ -1,4 +1,4 @@
make-flow
=========
-Make style control flow util
+Build systems style control flow util.
1  index.js
View
@@ -0,0 +1 @@
+module.exports = require('./lib/index')
134 lib/index.js
View
@@ -0,0 +1,134 @@
+var parseDeps = require('./parse-deps')
+
+module.exports = Flow
+
+function Flow () {}
+
+Flow.prototype.def = function (task, deps, fn) {
+ if (typeof deps == 'function') {
+ fn = deps
+ deps = fn.deps
+ }
+ this['_fn_' + task] = fn || function noop () {}
+ this['_deps_' + task] = deps || parseDeps(fn)
+ return this
+}
+
+Flow.prototype.eval = function (task, cb) {
+ if (this[task] !== undefined) {
+ this[task] instanceof Error
+ ? cb.call(this, this[task])
+ : cb.call(this, null, this[task])
+ return this
+ }
+ var promise = '_promise_' + task
+ if (!this[promise]) {
+ this[promise] = new Promise(this, task)
+ cb && this[promise].ondone(cb)
+ this[promise].eval()
+ } else {
+ cb && this[promise].ondone(cb)
+ }
+ return this
+}
+
+Flow.prototype.run = function (task, cb) {
+ var instance = Object.create(this)
+ task && instance.eval(task, cb)
+ return instance
+}
+
+Flow.prototype.set = function (name, val) {
+ this[name] = val
+ return this
+}
+
+Flow.prototype.parent = function (obj) {
+ this._parent = obj
+ return this
+}
+
+
+function Promise (flow, task) {
+ this.flow = flow
+ this.task = task
+ this.fn = this.flow['_fn_' + task]
+ if (this.fn) {
+ this.deps = this.flow['_deps_' + task]
+ } else {
+ this.fn = this.flow._parent && function (done) {
+ this._parent.eval(task, done)
+ }
+ if (!this.fn) throw new Error('Task "' + task + '" is not defined')
+ this.deps = ['done']
+ }
+ this.callbacks = []
+}
+
+Promise.prototype.ondone = function (cb) {
+ this.callbacks.push(cb)
+}
+
+Promise.prototype.eval = function () {
+ this.deps.length ? this.evalWithDeps(0) : this.exec()
+}
+
+Promise.prototype.evalWithDeps = function (index) {
+ var sync = true, self = this
+ while (sync) {
+ var dep = this.deps[index]
+ if (!dep) return this.exec()
+ if (dep == 'done') {
+ this.async = true
+ this.deps[index++] = this.done.bind(this)
+ continue
+ }
+ var val = this.flow[dep]
+ if (val !== undefined) {
+ if (val instanceof Error) return this.done(val)
+ this.deps[index++] = val
+ continue
+ }
+ var done = false
+ this.flow.eval(dep, function (err, val) {
+ if (err) return self.done(err)
+ done = true
+ self.deps[index++] = val
+ if (sync) return
+ self.deps.length > index // safe stack space if it's easy to safe
+ ? self.evalWithDeps(index)
+ : self.exec()
+ })
+ sync = done
+ }
+}
+
+Promise.prototype.exec = function () {
+ try {
+ if (this.async) {
+ this.fn.apply(this.flow, this.deps)
+ } else {
+ this.done(null, this.fn.apply(this.flow, this.deps))
+ }
+ } catch (e) {
+ this.done(e)
+ }
+}
+
+Promise.prototype.done = function (err, val) {
+ if (err != null) {
+ if (!err instanceof Error) {
+ err = new Error(String(err))
+ }
+ err.task = err.task || this.task
+ this.flow[this.task] = err
+ } else {
+ val = val === undefined ? null : val
+ this.flow[this.task] = val
+ }
+ delete this.flow['_promise_' + this.task] // cleanup
+ for (var i = 0; i < this.callbacks.length; i++) {
+ this.callbacks[i].call(this.flow, err, val)
+ }
+}
+
18 lib/parse-deps.js
View
@@ -0,0 +1,18 @@
+module.exports = function (fn) {
+ var src = fn.toString()
+ .replace(/(\/\*([\s\S]*?)\*\/|\/\/(.*)$)/mg, '') // remove comments
+
+ var m = /^function\s*\w*\s*\(([^\)]*)\)/.exec(src)
+ return m
+ ? m[1].split(',').map(trim).filter(nonEmpty)
+ : []
+}
+
+
+function trim (s) {
+ return s.trim()
+}
+
+function nonEmpty (s) {
+ return !!s
+}
23 package.json
View
@@ -0,0 +1,23 @@
+{
+ "name": "make-flow",
+ "version": "0.0.1",
+ "description": "Build systems style control flow",
+ "scripts": {
+ "test": "mocha -R spec"
+ },
+ "repository": {
+ "type": "git",
+ "url": "git://github.com/eldargab/make-flow.git"
+ },
+ "keywords": [
+ "async",
+ "control flow"
+ ],
+ "devDependencies": {
+ "test-log": "*",
+ "mocha": "*",
+ "should": "*"
+ },
+ "author": "Eldar Gabdullin <eldargab@gmail.com>",
+ "license": "MIT"
+}
140 test/flow.js
View
@@ -0,0 +1,140 @@
+var should = require('should')
+var Log = require('test-log')
+var Flow = require('../lib/index')
+
+describe('make-flow', function () {
+ var flow, log
+
+ beforeEach(function () {
+ flow = new Flow
+ log = Log()
+ })
+
+ describe('.eval(task, [cb])', function () {
+ it('Should call task function', function (done) {
+ flow.def('foo', function () {
+ return 'bar'
+ })
+ flow.eval('foo', function (err, val) {
+ val.should.equal('bar')
+ done()
+ })
+ })
+
+ it('Should treat task with <done> argument as async', function (done) {
+ var end
+ flow.def('foo', function (done) {
+ end = done
+ })
+ flow.eval('foo', function (err, val) {
+ val.should.equal('ok')
+ done()
+ })
+ end(null, 'ok')
+ })
+
+ it('Should call callback with <this> set to <flow>', function () {
+ flow.def('foo', function () {
+ return 'bar'
+ }).eval('foo', function () {
+ this.should.equal(flow)
+ })
+ })
+
+ it('Should call task with `this` set to flow', function (done) {
+ flow.def('foo', function () {
+ this.should.equal(flow)
+ done()
+ }).eval('foo')
+ })
+
+ it('Should store result in <this[task_name]>', function () {
+ flow.def('a', function () {
+ return 'b'
+ })
+ should.ok(flow.a === undefined)
+ flow.eval('a')
+ flow.a.should.equal('b')
+ })
+
+ it('Should set <this[task_name]> to <null> when the result is <undefined>', function () {
+ flow.def('undefined', function () {}).eval('undefined')
+ should.ok(flow['undefined'] === null)
+ })
+
+ it('Should not call task function when <this[task_name]> is not <undefined>', function (done) {
+ flow.hello = 10
+ flow.def('hello', log.fn('hello'))
+ flow.eval('hello', function (err, hello) {
+ hello.should.equal(10)
+ log.should.be.empty
+ done()
+ })
+ })
+
+ it('Should evaluate all task dependencies before evaluating task itself', function () {
+ var b_end, c_end, d_end
+
+ flow
+ .def('a', function (b, c, d) {
+ log('a')
+ })
+ .def('b', function (c, done) {
+ log('b')
+ b_end = done
+ })
+ .def('c', function (done) {
+ log('c')
+ c_end = done
+ })
+ .def('d', function (done) {
+ log('d')
+ d_end = done
+ })
+ .eval('a', function () {
+ log('done')
+ })
+
+ log.should.equal('c')
+ c_end()
+ log.should.equal('c b')
+ b_end()
+ log.should.equal('c b d')
+ d_end()
+ log.should.equal('c b d a done')
+ })
+
+ it('Should call parent for unknown tasks', function (done) {
+ var parent = new Flow
+ parent.def('a', function () { return 'a' })
+ flow.parent(parent).eval('a', function (err, val) {
+ val.should.equal('a')
+ done()
+ })
+ })
+ })
+
+ describe('.run()', function () {
+ it('Should create new instance with everything been inherited', function () {
+ flow.def('foo', function () {
+ return 'bar'
+ })
+ var New = flow.run().eval('foo')
+ New.foo.should.equal('bar')
+ should.not.exist(flow.foo)
+ })
+ })
+
+ describe('Error handling', function () {
+ it('Should catch task exceptions', function (done) {
+ flow.def('hello', function () {
+ throw 'hello error'
+ })
+ flow.eval('hello', function (err) {
+ err.should.equal('hello error')
+ done()
+ })
+ })
+ })
+})
+
1  test/mocha.opts
View
@@ -0,0 +1 @@
+--require should
32 test/parse-deps.js
View
@@ -0,0 +1,32 @@
+var parse = require('../lib/parse-deps')
+
+describe('Parse deps', function () {
+ it('Should parse deps of named function', function () {
+ parse(function hello (req, res, next) {})
+ .should.eql(['req', 'res', 'next'])
+ })
+
+ it('Should parse deps of anonymus function', function () {
+ parse(function(hello, world){})
+ .should.eql(['hello', 'world'])
+ })
+
+ it('Should ignore comments', function () {
+ parse(function (
+ a, //, b, c
+ /* e, f, */
+ d
+ ) {}).should.eql(['a', 'd'])
+ })
+
+ it('Should return empty array if there is no deps', function () {
+ parse(function () {}).should.eql([])
+ })
+
+ it('Should not parse nested things', function () {
+ parse(function () {
+ function hello (a, b) {
+ }
+ }).should.eql([])
+ })
+})
Please sign in to comment.
Something went wrong with that request. Please try again.