Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
eldargab committed Sep 3, 2012
0 parents commit c1d43fb
Show file tree
Hide file tree
Showing 11 changed files with 615 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .gitignore
@@ -0,0 +1,3 @@
*.sublime-project
*.sublime-workspace
node_modules/
1 change: 1 addition & 0 deletions index.js
@@ -0,0 +1 @@
module.exports = require('./lib/app')
138 changes: 138 additions & 0 deletions lib/app.js
@@ -0,0 +1,138 @@
var resolve = require('./path-resolve')
var parseDeps = require('./parse-dependencies')
var Box = require('./box')

module.exports = App

function App () {}

App.prototype.use = function (fn, var_args) {
fn.apply(this, [].slice.call(arguments, 1))
return this
}

App.prototype.on = function (path, deps, fn) {
if (typeof deps == 'function') {
fn = deps
deps = fn.deps || parseDeps(fn)
}
fn = fn || NOOP
deps = deps || []
var p = this._resolve(path)
this['_box_proto_' + p] = new Box(p, deps, fn)
return this
}

App.prototype.run = function (path) {
var instance = Object.create(this)
path && instance.eval(this._resolve(path))
return instance
}

App.prototype.eval = function (path, cb) {
var box = this._box(this._resolve(path))
cb = cb || NOOP
if (box.isReady) {
cb(box.val)
return
}
box.eval(this, cb)
return this
}

App.prototype._box = function (p) {
var box = this['_box_' + p]
if (box) return box
var proto = this['_box_proto_' + p]
if (!proto) throw new Error('Box ' + p + ' is not defined')
return this['_box_' + p] = Object.create(proto)
}

App.prototype.get = function (path) {
var p = this._resolve(path)
var box = this._box(p)
if (!box.isReady) throw new Error('Box ' + p + 'is not yet evaluated')
return box.val
}

App.prototype.set = function (path, val) {
this['_box_' + this._resolve(path)] = new Box.Evaluated(val)
return this
}

App.prototype.isReady = function (path) {
var key = '_box_' + this._resolve(path)
return !!this[key] && this[key].isReady
}

App.prototype.onerror = function (path, fn) {
if (typeof path == 'function') {
fn = path
path = '&'
}
this['_onerror_' + this._resolve(path)] = fn
return this
}

App.prototype._raise = function (p, e) {
var handler, parent = p, self = this

do {
p = parent
handler = this['_onerror_' + p]
parent = p.replace(/\/[^\/]*$/g, '') // trim last path segment
if (parent == p) parent = ''
} while (!handler)

var proxy = p && (new Proxy(this, p))

function raise (err) {
if (err == null) return
if (p == parent) throw err
self._raise(parent, err)
}

try {
handler.call(proxy, e, raise)
} catch (err) {
raise(err)
}
}

App.prototype._onerror_ = function (e) {
throw e
}

App.prototype._resolve = function (path) {
return resolve('', path)
}


App.prototype.prefix = function (p) {
return new Proxy(this, p)
}


function Proxy (app, prefix) {
this.prefix = prefix
this.app = app
}

;['get', 'set', 'isReady'].forEach(function (meth) {
Proxy.prototype[meth] = function (path) {
path = resolve(this.prefix, path)
return this.app[meth].apply(this.app, arguments)
}
})

;['on', 'onerror', 'eval'].forEach(function (meth) {
Proxy.prototype[meth] = function (path) {
path = resolve(this.prefix, path)
this.app[meth].apply(this.app, arguments)
return this
}
})

Proxy.prototype.use = App.prototype.use

function NOOP () {}
80 changes: 80 additions & 0 deletions lib/box.js
@@ -0,0 +1,80 @@
var resolve = require('./path-resolve')

module.exports = Box
module.exports.Evaluated = EvaluatedBox

function Box (path, deps, fn) {
this.isReady = false
this.evaluating = false
this.fn = fn
this.path = path
this.isSync = fn.length < 2
this.deps = deps.map(function (dep) {
return resolve(path, dep)
})
}

Box.prototype.eval = function (app, cb) {
cb && this.onready(cb)
if (this.evaluating) return
this.evaluating = true
this.evalDependencies(app, 0, this.execute)
}

Box.prototype.evalDependencies = function (app, index, cb) {
var self = this, sync = true
while (sync) {
var dep = this.deps[index++]
if (!dep) return cb.call(this, app)
var box = app._box(dep)
if (box.isReady) continue
var done = false
box.eval(app, function () {
done = true
if (sync) return
self.evalDependencies(app, index, cb)
})
sync = done
}
}

Box.prototype.execute = function (app) {
var proxy = app.prefix(this.path)
var self = this

function get (p) {
return proxy.get(p)
}

try {
this.isSync
? this.done(this.fn.call(proxy, get))
: this.fn.call(proxy, get, function (error, val) {
if (error) return app._raise(self.path, error)
self.done(val)
})
} catch (e) {
app._raise(this.path, e)
}
}

Box.prototype.done = function (val) {
this.isReady = true
this.val = val
this.callbacks && this.callbacks.forEach(function (cb) {
cb(val)
})
this.callbacks = null
}

Box.prototype.onready = function (cb) {
this.callbacks = this.callbacks || []
this.callbacks.push(cb)
}


function EvaluatedBox (val) {
this.val = val
}

EvaluatedBox.prototype.isReady = true
19 changes: 19 additions & 0 deletions lib/parse-dependencies.js
@@ -0,0 +1,19 @@

module.exports = function parseDependencies (fn) {
var deps = []
var src = fn.toString()

src = src.replace(/(\/\*([\s\S]*?)\*\/|\/\/(.*)$)/mg, '') // remove comments

var m = /^function(?:\s+\w+)?\s*\((\w+)/.exec(src) // determine the name of the get function
if (!m) return deps
var get = m[1]

var regex = new RegExp('(\\.\\s*)?' + get + '\\(\\s*["\']([^\'"\\s]+)["\']\\s*\\)', 'g')

src.replace(regex, function (_, isProp, dep) {
!isProp && deps.push(dep)
})

return deps
}
21 changes: 21 additions & 0 deletions lib/path-resolve.js
@@ -0,0 +1,21 @@

module.exports = function resolvePath (from, to) {
if (to[0] != '.' && to[0] != '&') return to

var path = from.split('/')
var segs = to.split('/')
if (to[0] != '&') path.pop()
segs.forEach(function (seg, index) {
if (seg == '.' || seg == '&') return
if (seg == '..') {
path.pop()
return
}
path.push(seg)
})
return trimSlash(path.join('/'))
}

function trimSlash (s) {
return s[0] == '/' ? s.slice(1) : s
}
16 changes: 16 additions & 0 deletions package.json
@@ -0,0 +1,16 @@
{
"name": "the-box",
"version": "0.0.0",
"description": "Build systems style computational model for the web",
"scripts": {
"test": "mocha -R spec"
},
"repository": "https://github.com/eldargab/the-box.git",
"devDependencies": {
"should": "*",
"mocha": "*",
"sinon": "*"
},
"author": "Eldar Gabdullin <eldargab@gmail.com>",
"license": "MIT"
}

0 comments on commit c1d43fb

Please sign in to comment.