objective
Version:
UNDER CONSTRUCTION
304 lines (258 loc) • 7.86 kB
JavaScript
// TODO - multiple requiring is problemattic, things are started over and over
// eg. pipeline sigint listener
// TODO - leaking unintended globals eg name
// var version = process.version.split(/\./)[1];
// if (version < 9) {
// console.error('node version '+process.version+' too old.');
// process.exit(1);
// }
function Objective() {
var args = handleArgs(arguments);
return Objective.run(args.config, args.callback);
};
Objective.getCaller = function(depth, cwdLess) {
var e = new ObjectiveError();
e.frames.shift();
if (cwdLess) e.frames[depth].file = e.frames[depth].file.replace(process.cwd() + sep, '');
return e.frames[depth]
}
Objective.argsOf = function(fn) {
return fn.toString()
.replace(/\/\*.*?\*\//g,'')
.replace(/\/\/.*/g,'')
.replace(/\n/g,'')
.replace(/\r/g,'')
.replace(/\/\*.*?\*\//g,'')
.match(/function\s*\((.*?)\)/)[1]
.replace(/\s/g, '').split(',')
.filter(function(arg) {
if (arg !== '') return true;
});
}
if (typeof objective == 'undefined') {
Object.defineProperty(global, 'objective', {
get: function() {
return Objective;
},
configurable: false,
enumerable: true
});
}
module.exports = Objective;
require('./objective/error');
objective.logger = require('./objective/logger');
objective.globals = require('./objective/globals');
objective.dev = {reporters: require('../node_modules/objective_dev/lib/reporters')}
objective.plugins = (objective.plugins || {});
objective.pluginsLoaded = (objective.pluginsLoaded || {});
var debug = objective.logger.createDebug('objective');
var TODO = objective.logger.TODO;
var warn = objective.logger.warn;
var error = objective.logger.error;
var root = require('./objective/root');
var child = require('./objective/child');
var replServer = require('./objective/repl/server');
var client = require('./objective/repl/client');
var sep = require('path').sep;
var dirname = require('path').dirname;
var fs = require('fs');
var program;
TODO('support nested or multiple objective per file (or prevent them');
if (typeof objective.rootWaiting == 'undefined') {
Object.defineProperty(objective, 'rootWaiting', {
get: function() {
return root.nextDeferral();
},
configurable: false,
enumerable: false
});
}
if (typeof objective.childWaiting == 'undefined') {
Object.defineProperty(objective, 'childWaiting', {
get: function() {
// when multiple child objectives are called to run
// each has a promise - to enable running one at a time
return child.nextDeferral();
},
configurable: true,
enumerable: false
});
}
Objective.run = function(config, callback) {
debug('run', arguments);
var filename = objective.getCaller(2).file;
if (filename.match(new RegExp("objective_dev"+sep+"lib"+sep+"index.js"))) {
filename = objective.getCaller(3).file;
}
var home, childsRoot;
// load json config if present
try {
jsonConfig = JSON.parse(fs.readFileSync(filename + '.json').toString());
var changes = Objective.merge(config, jsonConfig);
debug('merged config json', jsonConfig);
for (var key in changes) {
warn('objective config clash with json for %s, keeping \'%s\' from objective', key, changes[key].now);
}
} catch(e) {
if (e.code != 'ENOENT') {
error('error parsing json config %s', filename + '.json');
console.log(e);
process.exit(1);
}
}
debug('config loaded');
if (program.once) config.once = true;
objective.roots = (objective.roots || []);
if (typeof objective.loadAsRoot == 'undefined' || !objective.loadAsRoot || objective.loadingChildFrom) {
if (objective.roots.length > 0 || objective.noRoot) {
debug('loading child');
if (objective.loadingChildFrom) {
childsRoot = module.exports.findRoot({uuid:objective.loadingChildFrom});
home = childsRoot.home;
filename = filename.replace(home + sep, '');
} else if (objective.roots.length >= 0) {
console.log(objective.roots);
error('re-implelemnt child loading blind');
return;
}
config.filename = filename;
return child.load(childsRoot, config, callback);
}
}
if (program.attach) {
debug('attaching to remote objective');
client.connect(config);
return {run:function(){}} // run nothing when --attach
}
debug('loading root');
// use fullpath filename if no uuid on root
config.uuid = (config.uuid || filename);
// root has home
home = dirname(filename);
replServer.start(config);
return root.load(home, config, callback);
};
Objective.findRoot = function(search) {
var found;
objective.roots.forEach(function(root) {
if (root.config.uuid == search.uuid) found = root;
});
return found;
}
Objective.merge = function(config1, config2) {
// config2 values are merged into config1 one
// where vlues differ, config1 wins
// returns report on changes
var changes = {};
var getValueAt = function(object, path) {
try {
var at = object;
path.forEach(function(key) {
at = at[key];
});
return at;
} catch(e) {
return void 0;
}
}
var setValueAt = function(object, path, value) {
var at = object;
var count = 0
path.forEach(function(key) {
var last = path.length == ++count
var pat = at;
at = at[key];
if (at) {
if (last) {
at = value;
return
}
} else {
if (last) {
pat[key] = value;
} else {
at = pat[key] = {}
}
}
});
}
var recurse = function(node, path) {
if (!path) path = [];
if (typeof node === 'object' && ! (node instanceof Array)) {
for (var key in node) {
path.push(key);
recurse(node[key], path)
path.pop();
}
return;
}
var value1 = getValueAt(config1, path);
var value2 = getValueAt(config2, path);
if (typeof value1 === 'undefined') {
// apply value from config2 to config1
setValueAt(config1, path, value2)
} else {
if (value1 instanceof Array || value2 instanceof Array) {
// console.log('array');
changes[path.join('.')] = {
was: value2.toString(),
now: value1.toString()
}
}
else if (value1 != value2) {
changes[path.join('.')] = {
// config1 overrides config2 in args
was: value2,
now: value1
}
}
}
}
recurse(config2);
return changes;
}
// run the command line
program = require('./objective/program');
program.start()
objective.program = program;
function handleArgs(args) {
// a running objective, arguments behave dynamically
// organise them...
var title = args[0];
var config = args[1];
var callback = args[2];
if (typeof title === 'string') {
if (typeof config === 'object') {
config.title = title;
}
else if (typeof config === 'undefined' || config === null) {
config = {title: title};
}
else if (typeof config === 'function') {
callback = config;
config = {title: title};
}
}
if (typeof title === 'object') {
if (typeof config === 'function') {
config = title;
callback = config;
} else {
config = title;
}
}
if (typeof title === 'function') {
callback = title;
config = {}
}
if (typeof title === 'undefined' || title === null) {
config = {title: 'Untitled Objective'};
}
if (typeof config.title === "undefined" || config.title === null) {
config.title = 'Untitled Objective'
}
return {
config: config,
callback: callback
}
}