subsystems
Version:
simple dependency injection
399 lines (355 loc) • 9.68 kB
JavaScript
// Generated by CoffeeScript 1.10.0
(function() {
var OK, SubsystemInjectorStub, api_diff, build_context, execute_context, fmap, inject, isFn, isInjector, jstr, make_deps, make_edges, map2system, rename, scan, start_map, subsystem, topo, util, wrap,
bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
slice = [].slice;
topo = require('toposort');
util = require('util');
jstr = function(o) {
return util.inspect(o, {
depth: 5
});
};
isFn = function(o) {
return typeof o === 'function';
};
OK = null;
SubsystemInjectorStub = (function() {
function SubsystemInjectorStub(dep1) {
this.dep = dep1;
this;
}
return SubsystemInjectorStub;
})();
isInjector = function(obj) {
return obj instanceof SubsystemInjectorStub;
};
inject = function(dependency_name) {
if (dependency_name === 'start') {
throw new Error("No dependency may be called 'start'.");
}
return new SubsystemInjectorStub(dependency_name);
};
this.inject = inject;
scan = function(obj) {
var k, results, v;
results = [];
for (k in obj) {
v = obj[k];
if (isInjector(v)) {
results.push([k, v.dep]);
}
}
return results;
};
make_edges = function(name, scanned) {
var dep, field, i, len, ref, results;
results = [];
for (i = 0, len = scanned.length; i < len; i++) {
ref = scanned[i], field = ref[0], dep = ref[1];
results.push([dep, name]);
}
return results;
};
make_deps = function(map_subs, cont) {
var dep, deps_edges, nexts, scanned, sub;
deps_edges = [];
nexts = {};
for (dep in map_subs) {
sub = map_subs[dep];
scanned = scan(sub);
deps_edges = deps_edges.concat(make_edges(dep, scanned));
if (!isFn(sub.start)) {
return cont(new Error("Passed subsystem doesn't have start method - " + (jstr(dep)) + ":" + (jstr(sub)) + "."));
}
nexts[dep] = (function(dep, scanned, sub, starter) {
return function(started, cont) {
var field, i, len, ref, sub_dep;
sub.start = function(cont) {
return cont(OK, started[dep]);
};
if (dep in started) {
return cont(OK, started[dep]);
}
for (i = 0, len = scanned.length; i < len; i++) {
ref = scanned[i], field = ref[0], sub_dep = ref[1];
if (!(sub_dep in started)) {
return cont(new Error("Unmet dependency: " + (jstr(sub_dep)) + "."));
}
sub[field] = started[sub_dep];
}
return starter.call(sub, cont);
};
})(dep, scanned, sub, sub.start);
}
return cont(OK, deps_edges, nexts);
};
build_context = function(nodes, nexts, started, final_cont) {
return {
index: 0,
nodes: nodes,
nexts: nexts,
started: started,
cont: final_cont
};
};
execute_context = function(cxt) {
var dep;
if (cxt.index >= cxt.nodes.length) {
return cxt.cont(OK, cxt.started);
}
dep = cxt.nodes[cxt.index++];
if (dep in cxt.started) {
return execute_context(cxt);
}
return cxt.nexts[dep](cxt.started, function(err, sub_api) {
if (err) {
return cxt.cont(err);
}
cxt.started[dep] = sub_api;
return execute_context(cxt);
});
};
start_map = function(map, started, final_cont) {
return make_deps(map, function(err, deps, nexts) {
var dep, error, nodes, unmets, unsorted_nodes;
if (err) {
return final_cont(err);
}
unsorted_nodes = (function() {
var k, results, v;
results = [];
for (k in map) {
v = map[k];
results.push(k);
}
return results;
})();
try {
nodes = topo.array(unsorted_nodes, deps);
} catch (error) {
err = error;
return final_cont(err);
}
unmets = (function() {
var i, len, results;
results = [];
for (i = 0, len = nodes.length; i < len; i++) {
dep = nodes[i];
if (!((nexts[dep] != null) || dep in started)) {
results.push(jstr(dep));
}
}
return results;
})();
if (unmets.length !== 0) {
return final_cont(new Error("Unmet dependencies: " + unmets + "."));
}
return execute_context(build_context(nodes, nexts, started, final_cont));
});
};
map2system = function(map) {
var k, met_keys, unmet_keys, v;
unmet_keys = [];
met_keys = [];
for (k in map) {
v = map[k];
if (isInjector(v)) {
unmet_keys.push(k);
} else {
met_keys.push(k);
}
}
map.start = function(cont) {
var i, j, len, len1, started, unstarted;
started = {};
unstarted = {};
for (i = 0, len = unmet_keys.length; i < len; i++) {
k = unmet_keys[i];
if (isInjector(map[k])) {
cont(new Error("System with unmet dependency: " + (jstr(k)) + "."));
}
started[k] = map[k];
}
for (j = 0, len1 = met_keys.length; j < len1; j++) {
k = met_keys[j];
unstarted[k] = map[k];
}
return start_map(unstarted, started, cont);
};
return map;
};
this.system = function(map) {
if (map.start != null) {
throw new Error("No dependency may be called 'start'.");
}
return map2system(map);
};
this.start = function(system, cont) {
var dep, k, s;
s = scan(system);
if (s.length !== 0) {
return cont(new Error("Cannot start system with unmet dependencies: " + ((function() {
var i, len, ref, results;
results = [];
for (i = 0, len = s.length; i < len; i++) {
ref = s[i], k = ref[0], dep = ref[1];
results.push(jstr(dep));
}
return results;
})()) + "."));
} else {
if (!isFn(system.start)) {
return cont(new Error('No start method on system.'));
} else {
return system.start(cont);
}
}
};
fmap = function(dep, fn) {
return new ((function() {
function _Class() {
this.start = bind(this.start, this);
}
_Class.prototype.value = inject(dep);
_Class.prototype.start = function(cont) {
var err, error, v;
try {
v = fn(this.value);
} catch (error) {
err = error;
return cont(err);
}
return cont(OK, v);
};
return _Class;
})());
};
this.fmap = fmap;
this.field = function() {
var dep, fields;
dep = arguments[0], fields = 2 <= arguments.length ? slice.call(arguments, 1) : [];
return fmap(dep, function(v) {
var field, i, len, ret;
ret = v;
for (i = 0, len = fields.length; i < len; i++) {
field = fields[i];
if (!(field in Object(ret))) {
throw new Error("Field " + (jstr(field)) + " not present.");
}
ret = ret[field];
}
return ret;
});
};
rename = function(system, map) {
var dep, i, key, len, old, ref, s;
s = scan(system);
for (i = 0, len = s.length; i < len; i++) {
ref = s[i], key = ref[0], old = ref[1];
if (dep = map[old]) {
system[key] = inject(dep);
}
}
return system;
};
this.rename = function(system, map) {
if (map.start != null) {
throw new Error("No dependency may be called 'start'.");
}
return rename(system, map);
};
wrap = function(api) {
return {
start: function(cont) {
return cont(OK, api);
}
};
};
this.wrap = wrap;
api_diff = function(ss) {
var k, reference, v;
reference = {};
for (k in ss) {
v = ss[k];
reference[k] = v;
}
return function() {
var api;
api = {};
for (k in ss) {
v = ss[k];
if (reference[k] !== v) {
api[k] = v;
}
}
return api;
};
};
subsystem = function(core) {
return function(deps, ctor) {
if (deps.start != null) {
throw new Error("No dependency field may be called 'start'.");
}
return function() {
var k, ss, v;
ss = {};
for (k in deps) {
v = deps[k];
ss[k] = inject(v);
}
ss.start = core(ctor.bind(ss), ss);
return ss;
};
};
};
subsystem.sync = function(ctor, ss) {
return function(cont) {
var err, error, mk_api;
mk_api = api_diff(ss);
try {
ctor();
} catch (error) {
err = error;
return cont(err);
}
return cont(OK, mk_api());
};
};
subsystem.async = function(ctor, ss) {
return function(cont) {
var mk_api;
mk_api = api_diff(ss);
try {
return ctor(function(err) {
if (err) {
return cont(err);
}
return cont(OK, mk_api());
});
} catch (undefined) {}
};
};
subsystem.ret = function(ctor, ss) {
return function(cont) {
var err;
return cont.apply(void 0, (function() {
var error;
try {
return [OK, ctor()];
} catch (error) {
err = error;
return [err];
}
})());
};
};
subsystem.ret.async = function(ctor, ss) {
return ctor;
};
this.subsystem = subsystem(subsystem.sync);
this.subsystem.async = subsystem(subsystem.async);
this.subsystem.ret = subsystem(subsystem.ret);
this.subsystem.ret.async = subsystem(subsystem.ret.async);
}).call(this);
//# sourceMappingURL=subsystems.js.map