UNPKG

noctua-repl

Version:

Interactive programming interface for Noctua (Barista->Minerva).

615 lines (511 loc) 16.1 kB
//// //// REPL environment for Noctua. //// //// See README.md for more information. //// // Util. var bbop = require('bbop-core'); var fs = require('fs'); var us = require('underscore'); var repl = require('repl'); var barista_response = require('bbop-response-barista'); var class_expression = require('class-expression'); var minerva_requests = require('minerva-requests'); var noctua_model = require('bbop-graph-noctua'); // var sync_engine = require('bbop-rest-manager').sync_request; var minerva_manager = require('bbop-manager-minerva'); // var anchor = this; /// /// Helpers /// var what_is = bbop.what_is; var each = us.each; function _die(message){ console.error(message); process.exit(-1); } /// /// Process CLI options. /// var argv = require('minimist')(process.argv.slice(2)); //console.dir(argv); // Get the (pretty much required) token. var token = argv['t'] || argv['token']; if( ! token || (what_is(token) !== 'string' && what_is(token) !== 'number' ) ){ _die('Option (t|token) is required.'); }else{ console.log('Using user token: ' + token); } // Aim at the proper/desired barista server. var barista_server = argv['s'] || argv['server']; //var barista_server_default = 'http://barista.berkeleybop.org'; var barista_server_default = 'http://localhost:3400'; if( ! barista_server || what_is(barista_server) !== 'string' ){ //_die('Option (s|server) is required.'); barista_server = barista_server_default; console.log('Using default Barista server at: ' + barista_server); }else{ console.log('Using Barista server at: ' + barista_server); } // Work again the proper/desired barista definition. var barista_definition = argv['d'] || argv['definition']; var barista_definition_default = 'minerva_local'; if( ! barista_definition || what_is(barista_definition) !== 'string' ){ //_die('Option (d|definition) is required.'); barista_definition = barista_definition_default; console.log('Using default Barista definition: ' + barista_definition); }else{ console.log('Using Barista definition: ' + barista_definition); } // The idea here is to be able to run a single command (line of // javascript). var command = argv['c'] || argv['command'] || null; if( ! command || what_is(command) !== 'string' ){ // Is optional; pass. }else{ console.log('Run command: ' + command); } // The idea here is to be able to run a set of commands in the // environment in batch. // TODO: (optional) file var file = argv['f'] || argv['file']; if( ! file || what_is(file) !== 'string' ){ // Is optional; pass. }else{ console.log('Run file: ' + file); } /// /// Spin up REPL and create running environment. /// // Start repl. var repl_run = repl.start({ 'prompt': 'noctua-repl@' + barista_server + '|' + barista_definition + '> ', 'input': process.stdin, 'output': process.stdout, 'useGlobal': true//, // // Try and keep everything in this scope. // 'eval': function(cmd, context, filename, callback){ // console.log(cmd); // callback(null, result); // } }); // Okay, this is a little hard to explain. It seems to be quite hard // to get this context to stay in sync with the REPLs running one once // it "forks". It seems like it somehow initially "copies" this // context and no long syncs after initialization. The exception seems // to be the context in repl_run. // // // To simplify this pattern, at the end of functions, to sync the // // environments, I use this pattern. The arguments are strings. // // // // Another, probably cleaner, way of doing this might be to write our // // own eval function and keep this context (anchor), but with so few // // examples, I'm williing to work with this for now. // function _contextualize(context, symbol, argument){ // if( ! argument ){ argument = symbol; } // often use single arg // // eval(symbol+" = "+argument+";"); // // eval("repl_run.context['"+symbol+"'] = "+argument+";"); // eval.call(context, symbol+" = "+argument+";"); // eval.call(context, "repl_run.context['"+symbol+"'] = "+argument+";"); // eval("repl_run.context['"+symbol+"'] = "+argument+";"); // } // Extract. function _get_current_model_id(){ var mid = repl_run.context['model_id'] || model_id; return mid; } // Make the request and save the interesting products. function _request_and_save(manager, request_set){ // Request, let the synxchonous callbacks deal with with happens. manager.request_with(request_set); // Capture the incoming request. repl_run.context['request_set'] = request_set; } // Add manager and default callbacks to repl. var engine = new sync_engine(barista_response); var manager = new minerva_manager(barista_server, barista_definition, token, engine, 'sync'); var request_set = null; var response = null; var model = null; var model_id = null; var query_url = null; // Try and make a general "good" response. function _good_response_handler(resp){ // "Display" the returning data. show(resp.data()); // Extract any model ID and assign it to the environment as the // default--probably only useful when a model is being created and // we want to add stuff on. var mid = resp.model_id(); if( mid ){ model_id = mid; repl_run.context['model_id'] = mid; }else{ model_id = null; repl_run.context['model_id'] = null; } // If there are models, set them up in the environment. var pre_model = new noctua_model.graph(); var d = resp.data(); if( d && pre_model.load_data_basic(d) ){ model = pre_model; repl_run.context['model'] = pre_model; }else{ model = null; repl_run.context['model'] = null; } // Add the response back into the REPL environment. repl_run.context['response'] = resp; response = resp; // TODO: can we easily extract this still? Probably not, what with // all the POST and engine abstractions we have now. OTOH, it's // easier to push tests upstream. repl_run.context['query_url'] = null; } // Generic way of handling problems during responses. function _bad_response_handler(type, resp, man){ // Deliver a mostly coherent error message. console.error('\n'); console.error('There was a '+ type + ' (' + resp.message_type() + '): ' + resp.message()); console.error('\n'); // If the response id defined, assign it back into the REPL. repl_run.context['response'] = resp; } // "prerun" callback. manager.register('prerun', function(){ console.log('Starting...'); }); // "postrun" callback. manager.register('postrun', function(){ //console.log('Completed.'); }); // "manager_error" callback. manager.register('manager_error', function(resp, man){ _bad_response_handler('manager error', resp, man); }); // "error" callback. manager.register('error', function(resp, man){ _bad_response_handler('error', resp, man); }); // "warning" callback. manager.register('warning', function(resp, man){ _bad_response_handler('warning', resp, man); }); // "meta" callback. manager.register('meta', function(resp, man){ _good_response_handler(resp); }); // "merge" callback. manager.register('merge', function(resp, man){ _good_response_handler(resp); }); // "rebuild" callback. manager.register('rebuild', function(resp, man){ _good_response_handler(resp); }); /// /// Activites. /// /** * Make best effort to show the structure of the given object. */ var SILENT = false; function show(x){ if( ! SILENT ){ if( x && x.structure ){ console.log(JSON.stringify(x.structure(), null, ' ')); }else{ console.log(JSON.stringify(x, null, ' ')); } } } /** Union of given class expressions. */ var union = class_expression.union; /** Intersection of given class expressions. */ var intersection = class_expression.intersection; /** SVF attempt. */ var svf = class_expression.svf; /** Best attempt to contsruct class expressions. */ var cls = class_expression.cls; function get_meta(){ // Construct. request_set = new minerva_requests.request_set(token); request_set.get_meta(); _request_and_save(manager, request_set); } // Can be used to switch models, as well as view the current one. function get_model(mid){ if( ! mid ){ mid = _get_current_model_id(); } // Construct. request_set = new minerva_requests.request_set(token); request_set.get_model(mid); _request_and_save(manager, request_set); } // function add_model(){ // Construct. request_set = new minerva_requests.request_set(token); request_set.add_model(); _request_and_save(manager, request_set); } function save_model(mid){ if( ! mid ){ mid = _get_current_model_id(); } // Construct. request_set = new minerva_requests.request_set(token); request_set.store_model(mid); _request_and_save(manager, request_set); } function add_individual(cls_expr, ind_id){ var mid = _get_current_model_id(); // Construct. request_set = new minerva_requests.request_set(token, mid); request_set.add_individual(cls_expr, ind_id); _request_and_save(manager, request_set); } /** Create a custom request set from the current environment. */ function new_request_set(){ var mid = _get_current_model_id(); var reqs = new minerva_requests.request_set(token, mid); return reqs; } /** Add a custom request set; probably necessary for linking. */ function request_with(req_set){ _request_and_save(manager, req_set); } function silent(bool){ if( bool && bool === true ){ SILENT = true; }else{ SILENT = false; } } /** * */ function show_models(order_by){ // Quietly get the meta information. silent(true); var meta_resp = manager.get_meta(); silent(false); // Data capture step. var cache = []; var models_meta = meta_resp.models_meta(); each(models_meta, function(annotations, mid){ // Collect and bin all the annotations. var key_to_value_list = {}; each(annotations, function(ann){ var k = ann['key']; // Ensure list. if( typeof(key_to_value_list[k]) === 'undefined' ){ key_to_value_list[k] = []; } key_to_value_list[k].push(ann['value']); }); // Create the final (sortable) strings. // Annotations we care about. var title = '<no title>'; var date = '????-??-??'; var contributor = '???'; var state = '???'; var modified_p = ' '; var deprecated = ' '; if( key_to_value_list['title'] ){ title = key_to_value_list['title'].join("|"); } if( key_to_value_list['date'] ){ date = key_to_value_list['date'].join("|"); } if( key_to_value_list['contributor'] ){ contributor = key_to_value_list['contributor'].join("|"); } if( key_to_value_list['state'] ){ state = key_to_value_list['state'].join("|"); } if( key_to_value_list['deprecated'] ){ deprecated = key_to_value_list['deprecated'].join("|"); } cache.push({ 'id': mid, 'date': date, 'modified-p': modified_p, 'state': state, 'deprecated': deprecated, 'contributor': contributor, 'title': title }); }); // Now get the information from the "read-only" stream of // key/values. var models_meta_ro = meta_resp.models_meta_read_only(); each(cache, function(item){ var mid = item['id']; if( models_meta_ro && models_meta_ro[mid] ){ if( models_meta_ro[mid]['modified-p'] ){ //item['modified-p'] = models_meta_ro['id']['modified-p']; //console.log('add mod 4 ' + mid); item['modified-p'] = '*'; } } }); // Optional sorting step. if( order_by ){ cache = cache.sort(function(a, b){ var cmp_a = a[order_by].toLowerCase(); var cmp_b = b[order_by].toLowerCase(); if( cmp_a > cmp_b ){ return 1; }else if( cmp_a === cmp_b ){ return 0; }else{ return -1; } }); } // Display the info nicely. each(cache, function(item){ console.log([ //item['id'] + ' ' + item['date'] + ' ' + item['modified'] + ' ' + item['deprecated'], item['id'], item['date'], item['modified-p'], item['state'], item['deprecated'], item['contributor'], item['title'], ].join("\t")); }); } /** * */ function show_response(){ var col = 15; // Already have it or not. if( response ){ var out = { 'okay': response.okay(), 'user-id': response.user_id(), 'message_type': response.message_type(), 'message': '"' + response.message() + '"', 'signal': response.signal(), 'intention': response.intention(), 'modified-p': response.modified_p(), 'inconsistent': response.inconsistent_p(), 'has_undo': response.has_undo_p(), 'has_redo': response.has_redo_p(), 'facts': response.facts().length, 'individuals': response.individuals().length, 'evidence': response.evidence().length }; each(out, function(val, key){ // var spacing = col - key.length; if( spacing <= 0 ){ spacing = 1; } var spaces = ''; each(us.range(spacing), function(){ spaces += ' '; }); console.log(key + spaces + val); }); } } /// /// Export important things to REPL environment. /// var export_context = [ // Helpers. 'bbop', 'us', 'manager', 'show', // Auto-variables 'token', 'model', 'model_id', 'request_set', 'response', 'query_url', // Class expressions. 'union', 'intersection', 'svf', 'cls', // Manager actions. 'get_meta', 'get_model', 'add_model', 'save_model', 'add_individual', 'new_request_set', 'request_with', // Bigger fun macros. 'show_models', 'show_response', 'silent' ]; each(export_context, function(symbol){ eval("repl_run.context['"+symbol+"'] = "+symbol+";"); }); /// /// Run command or file in our environment. /// if( command ){ // Run command. console.log(''); //console.log('command: ', command); eval(command + ";"); // Exit. process.exit(0); } if( file ){ fs.readFile(file, function (err, data) { if(err){ throw err; } //console.log(data); // Run file. //console.log() eval(data.toString()); // Exit. process.exit(0); }); } /// /// REPL examples as we move forward with testing. /// // get_meta(); // model_id = 'gomodel:taxon_9606-5539842e0000002' // add_individual('GO:0022008'); // add_individual(intersection(['GO:0022008', 'GO:0008150'])) // add_model() // var r = new_request_set() // r.add_fact([r.add_individual('GO:0022008'), r.add_individual('GO:0008150'), 'part_of'] ) // request_with(r) // get_model('gomodel:taxon_559292-5525a0fc0000001_all_indivdual') // request_with(new_request_set().add_annotation_to_fact('comment', 'foo', null, ['gomodel:taxon_559292-5525a0fc0000001-GO-0005515-5525a0fc0000023','gomodel:taxon_559292-5525a0fc0000001-GO-0005095-5525a0fc0000009','RO:0002408'])); // get_model('gomodel:taxon_559292-5525a0fc0000001_all_indivdual') // request_with(new_request_set().add_evidence('ECO:0000034', 'NEMO:0000001', ['gomodel:taxon_559292-5525a0fc0000001-GO-0005095-5525a0fc0000009','gomodel_taxon_559292-5525a0fc0000001-SGD-S000003814-553ff9ed0000002','RO:0002333'])); // get_model('gomodel:taxon_559292-5525a0fc0000001_all_indivdual') // request_with(new_request_set().remove_type_from_individual(cls('SGD:S000003814'), 'gomodel_taxon_559292-5525a0fc0000001-SGD-S000003814-553ff9ed0000002')); // request_with(new_request_set().add_type_to_individual(cls('SGD:S000003814'), 'gomodel_taxon_559292-5525a0fc0000001-SGD-S000003814-553ff9ed0000002')); // request_with(new_request_set().add_type_to_individual(cls('SGD:S000003815'), 'gomodel_taxon_559292-5525a0fc0000001-SGD-S000003814-553ff9ed0000002')); /// /// Some internal testing. /// // // Closure test--this works in repl. // var close_i = 2; // function incr(){ // if( typeof(close_i) !== 'undefined' ){ // close_i++; // console.log('close_i is: ' + close_i); // }else{ // console.error('no close_i'); // } // } // repl_run.context['incr'] = incr;