commit/galaxy-central: 3 new changesets
3 new commits in galaxy-central: https://bitbucket.org/galaxy/galaxy-central/commits/4ceb95d03fc6/ Changeset: 4ceb95d03fc6 User: dannon Date: 2015-01-07 19:51:11+00:00 Summary: Slightly bump the width of the Message field for bug reports. Affected #: 1 file diff -r 8c57fc528cb7255ab11d20b03b5dd8509e3ea5a0 -r 4ceb95d03fc6554aad5d63e5ab8be6da948121e8 templates/webapps/galaxy/dataset/errors.mako --- a/templates/webapps/galaxy/dataset/errors.mako +++ b/templates/webapps/galaxy/dataset/errors.mako @@ -99,7 +99,7 @@ </div><div class="form-row"><label>Message</label> - <textarea name="message" rows="10" cols="40"></textarea> + <textarea name="message" rows="10" cols="60"></textarea></div><div class="form-row"><input type="submit" name="submit_error_report" value="Report" onclick="return sendReport( this, this.form, '_self' );"/> https://bitbucket.org/galaxy/galaxy-central/commits/23fddbbd5153/ Changeset: 23fddbbd5153 User: dannon Date: 2015-01-07 19:51:24+00:00 Summary: Strip whitespace. Affected #: 1 file diff -r 4ceb95d03fc6554aad5d63e5ab8be6da948121e8 -r 23fddbbd5153ba3d6c8bbbbd33fcb5aa9225f35e templates/webapps/galaxy/dataset/errors.mako --- a/templates/webapps/galaxy/dataset/errors.mako +++ b/templates/webapps/galaxy/dataset/errors.mako @@ -32,7 +32,7 @@ { form.elements[i].disabled = true; } - + } var hiddenInput = document.createElement('input'); hiddenInput.type = 'hidden'; @@ -52,7 +52,7 @@ <p><b>Dataset ${hda.hid}: ${hda.display_name() | h}</b></p><% job = hda.creating_job %> %if job: - + %if job.traceback: The Galaxy framework encountered the following error while attempting to run the tool: <pre>${ util.unicodify( job.traceback ) | h}</pre> @@ -82,8 +82,8 @@ %><h2>Report this error to the Galaxy Team</h2><p> - The Galaxy team regularly reviews errors that occur in the application. - However, if you would like to provide additional information (such as + The Galaxy team regularly reviews errors that occur in the application. + However, if you would like to provide additional information (such as what you were trying to do when the error occurred) and a contact e-mail address, we will be better able to investigate your problem and get back to you. https://bitbucket.org/galaxy/galaxy-central/commits/85e8fb54b285/ Changeset: 85e8fb54b285 User: dannon Date: 2015-01-12 13:59:51+00:00 Summary: Merge. Affected #: 62 files diff -r 23fddbbd5153ba3d6c8bbbbd33fcb5aa9225f35e -r 85e8fb54b2851454a000cab23d57ce102af6aa30 client/galaxy/scripts/galaxy.workflow_editor.canvas.js --- a/client/galaxy/scripts/galaxy.workflow_editor.canvas.js +++ b/client/galaxy/scripts/galaxy.workflow_editor.canvas.js @@ -805,7 +805,7 @@ nodeView.addDataOutput( output ); } ); nodeView.render(); - workflow.node_changed( this ); + workflow.node_changed( this, true); }, update_field_data : function( data ) { var node = this; @@ -1094,9 +1094,9 @@ this.active_node = node; } }, - node_changed : function ( node ) { + node_changed : function ( node, force ) { this.has_changes = true; - if ( this.active_node == node ) { + if ( this.active_node == node && (!parent.__NEWTOOLFORM__ || force)) { // Reactive with new form_html this.check_changes_in_active_form(); //Force changes to be saved even on new connection (previously dumped) parent.show_form_for_tool( node.form_html + node.tooltip, node ); @@ -1305,6 +1305,12 @@ return node; } +function update_node( data ) { + var node = workflow.active_node; + if (node) { + node.update_field_data(data, true); + } +} var ext_to_type = null; var type_to_type = null; diff -r 23fddbbd5153ba3d6c8bbbbd33fcb5aa9225f35e -r 85e8fb54b2851454a000cab23d57ce102af6aa30 client/galaxy/scripts/mvc/tools/tools-form-base.js --- /dev/null +++ b/client/galaxy/scripts/mvc/tools/tools-form-base.js @@ -0,0 +1,315 @@ +/** + This is the main class of the tool form plugin. It is referenced as 'app' in all lower level modules. +*/ +define(['utils/utils', 'utils/deferred', 'mvc/ui/ui-portlet', 'mvc/ui/ui-misc', + 'mvc/citation/citation-model', 'mvc/citation/citation-view', + 'mvc/tools', 'mvc/tools/tools-template', 'mvc/tools/tools-content', 'mvc/tools/tools-section', 'mvc/tools/tools-tree'], + function(Utils, Deferred, Portlet, Ui, CitationModel, CitationView, + Tools, ToolTemplate, ToolContent, ToolSection, ToolTree) { + + // create form view + return Backbone.View.extend({ + // initialize + initialize: function(options) { + // log options + console.debug(options); + + // link galaxy modal or create one + var galaxy = parent.Galaxy; + if (galaxy && galaxy.modal) { + this.modal = galaxy.modal; + } else { + this.modal = new Ui.Modal.View(); + } + + // check if the user is an admin + if (galaxy && galaxy.currUser) { + this.is_admin = galaxy.currUser.get('is_admin'); + } else { + this.is_admin = false; + } + + // link options + this.options = options; + + // link container + this.container = this.options.container || 'body'; + + // create deferred processing queue handler + // this handler reduces the number of requests to the api by filtering redundant requests + this.deferred = new Deferred(); + + // set element + this.setElement('<div/>'); + + // add to main element + $(this.container).append(this.$el); + + // build this form + this._buildForm(); + }, + + /** Shows the final message (usually upon successful job submission) + */ + reciept: function($el) { + $(this.container).empty(); + $(this.container).append($el); + }, + + /** Highlight and scroll to input element (currently only used for error notifications) + */ + highlight: function (input_id, message, silent) { + // get input field + var input_element = this.element_list[input_id]; + + // check input element + if (input_element) { + // mark error + input_element.error(message || 'Please verify this parameter.'); + + // scroll to first input element + if (!silent) { + $(this.container).animate({ + scrollTop: input_element.$el.offset().top - 20 + }, 500); + } + } + }, + + /** Main tool form build function. This function is called once a new model is available. + */ + _buildForm: function() { + // link this + var self = this; + + // reset events + this.off('refresh'); + this.off('reset'); + + // reset field list, which contains the input field elements + this.field_list = {}; + + // reset sequential input definition list, which contains the input definitions as provided from the api + this.input_list = {}; + + // reset input element list, which contains the dom elements of each input element (includes also the input field) + this.element_list = {}; + + // creates a tree/json data structure from the input form + this.tree = new ToolTree(this); + + // request history content and build form + this.content = new ToolContent(this); + + // link model options + var options = this.options; + + // create ui elements + this._renderForm(options); + + // rebuild the underlying data structure + this.tree.finalize(); + + // show errors + if (!this.workflow && options.errors) { + var error_messages = this.tree.matchResponse(options.errors); + for (var input_id in error_messages) { + this.highlight(input_id, error_messages[input_id], true); + } + } + + // add refresh listener + this.on('refresh', function() { + // by using/reseting the deferred ajax queue the number of redundant calls is reduced + self.deferred.reset(); + self.deferred.execute(function(){self._updateModel()}); + }); + + // add reset listener + this.on('reset', function() { + for (var i in this.element_list) { + this.element_list[i].reset(); + } + }); + }, + + /** Renders the UI elements required for the form + */ + _renderForm: function(options) { + // link this + var self = this; + + // create message view + this.message = new Ui.Message(); + + // button for version selection + var requirements_button = new Ui.ButtonIcon({ + icon : 'fa-info-circle', + title : 'Requirements', + tooltip : 'Display tool requirements', + onclick : function() { + if (!this.visible) { + this.visible = true; + self.message.update({ + persistent : true, + message : ToolTemplate.requirements(options), + status : 'info' + }); + } else { + this.visible = false; + self.message.update({ + message : '' + }); + } + } + }); + if (!options.requirements || options.requirements.length == 0) { + requirements_button.$el.hide(); + } + + // button for version selection + var versions_button = new Ui.ButtonMenu({ + icon : 'fa-cubes', + title : 'Versions', + tooltip : 'Select another tool version' + }); + if (options.versions && options.versions.length > 1) { + for (var i in options.versions) { + var version = options.versions[i]; + if (version != options.version) { + versions_button.addMenu({ + title : 'Switch to ' + version, + version : version, + icon : 'fa-cube', + onclick : function() { + // here we update the tool version (some tools encode the version also in the id) + options.id = options.id.replace(options.version, this.version); + options.version = this.version; + + // rebuild the model and form + self.deferred.reset(); + self.deferred.execute(function(){self._buildModel()}); + } + }); + } + } + } else { + versions_button.$el.hide(); + } + + // button menu + var menu_button = new Ui.ButtonMenu({ + icon : 'fa-caret-down', + title : 'Options', + tooltip : 'View available options' + }); + + // configure button selection + if(options.biostar_url) { + // add question option + menu_button.addMenu({ + icon : 'fa-question-circle', + title : 'Question?', + tooltip : 'Ask a question about this tool (Biostar)', + onclick : function() { + window.open(options.biostar_url + '/p/new/post/'); + } + }); + + // create search button + menu_button.addMenu({ + icon : 'fa-search', + title : 'Search', + tooltip : 'Search help for this tool (Biostar)', + onclick : function() { + window.open(options.biostar_url + '/t/' + options.id + '/'); + } + }); + }; + + // create share button + menu_button.addMenu({ + icon : 'fa-share', + title : 'Share', + tooltip : 'Share this tool', + onclick : function() { + prompt('Copy to clipboard: Ctrl+C, Enter', window.location.origin + galaxy_config.root + 'root?tool_id=' + options.id); + } + }); + + // add admin operations + if (this.is_admin) { + // create download button + menu_button.addMenu({ + icon : 'fa-download', + title : 'Download', + tooltip : 'Download this tool', + onclick : function() { + window.location.href = galaxy_config.root + 'api/tools/' + options.id + '/download'; + } + }); + } + + // create tool form section + this.section = new ToolSection.View(self, { + inputs : options.inputs, + cls : 'ui-table-plain' + }); + + // switch to classic tool form mako if the form definition is incompatible + if (this.incompatible) { + this.$el.hide(); + $('#tool-form-classic').show(); + return; + } + + // create portlet + this.portlet = new Portlet.View({ + icon : 'fa-wrench', + title : '<b>' + options.name + '</b> ' + options.description + ' (Galaxy Tool Version ' + options.version + ')', + cls : 'ui-portlet-slim', + operations: { + requirements : requirements_button, + menu : menu_button, + versions : versions_button + }, + buttons : this.buttons + }); + + // append message + this.portlet.append(this.message.$el, true); + + // append tool section + this.portlet.append(this.section.$el); + + // start form + this.$el.empty(); + this.$el.append(this.portlet.$el); + + // append help + if (options.help != '') { + this.$el.append(ToolTemplate.help(options.help)); + } + + // append citations + if (options.citations) { + var $citations = $('<div/>'); + var citations = new CitationModel.ToolCitationCollection(); + citations.tool_id = options.id; + var citation_list_view = new CitationView.CitationListView({ el: $citations, collection: citations } ); + citation_list_view.render(); + citations.fetch(); + this.$el.append($citations); + } + + // show message if available in model + if (options.message) { + this.message.update({ + persistent : true, + status : 'warning', + message : options.message + }); + } + } + }); +}); diff -r 23fddbbd5153ba3d6c8bbbbd33fcb5aa9225f35e -r 85e8fb54b2851454a000cab23d57ce102af6aa30 client/galaxy/scripts/mvc/tools/tools-form-workflow.js --- /dev/null +++ b/client/galaxy/scripts/mvc/tools/tools-form-workflow.js @@ -0,0 +1,66 @@ +/** + This is the main class of the tool form plugin. It is referenced as 'app' in all lower level modules. +*/ +define(['utils/utils', 'mvc/tools/tools-form-base'], + function(Utils, ToolFormBase) { + + // create form view + var View = ToolFormBase.extend({ + initialize: function(options) { + this.workflow = true; + ToolFormBase.prototype.initialize.call(this, options); + }, + + /** Builds a new model through api call and recreates the entire form + */ + _buildModel: function() { + }, + + /** Request a new model for an already created tool form and updates the form inputs + */ + _updateModel: function() { + // create the request dictionary + var self = this; + var current_state = this.tree.finalize(); + + // log tool state + console.debug('tools-form-workflow::_refreshForm() - Refreshing states.'); + console.debug(current_state); + + // register process + var process_id = this.deferred.register(); + + // build model url for request + var model_url = galaxy_config.root + 'workflow/editor_form_post?tool_id=' + this.options.id; + + // post job + Utils.request({ + type : 'GET', + url : model_url, + data : current_state, + success : function(node) { + parent.update_node(node); + + // process completed + self.deferred.done(process_id); + + // log success + console.debug('tools-form::_refreshForm() - States refreshed.'); + console.debug(node); + }, + error : function(response) { + // process completed + self.deferred.done(process_id); + + // log error + console.debug('tools-form::_refreshForm() - Refresh request failed.'); + console.debug(response); + } + }); + } + }); + + return { + View: View + }; +}); diff -r 23fddbbd5153ba3d6c8bbbbd33fcb5aa9225f35e -r 85e8fb54b2851454a000cab23d57ce102af6aa30 client/galaxy/scripts/mvc/tools/tools-form.js --- a/client/galaxy/scripts/mvc/tools/tools-form.js +++ b/client/galaxy/scripts/mvc/tools/tools-form.js @@ -1,79 +1,28 @@ /** This is the main class of the tool form plugin. It is referenced as 'app' in all lower level modules. */ -define(['utils/utils', 'utils/deferred', 'mvc/ui/ui-portlet', 'mvc/ui/ui-misc', - 'mvc/citation/citation-model', 'mvc/citation/citation-view', - 'mvc/tools', 'mvc/tools/tools-template', 'mvc/tools/tools-content', 'mvc/tools/tools-section', 'mvc/tools/tools-tree', 'mvc/tools/tools-jobs'], - function(Utils, Deferred, Portlet, Ui, CitationModel, CitationView, - Tools, ToolTemplate, ToolContent, ToolSection, ToolTree, ToolJobs) { +define(['utils/utils', 'mvc/ui/ui-misc', 'mvc/tools/tools-form-base', 'mvc/tools/tools-jobs'], + function(Utils, Ui, ToolFormBase, ToolJobs) { // create form view - var View = Backbone.View.extend({ - // base element - container: 'body', - + var View = ToolFormBase.extend({ // initialize initialize: function(options) { - // log options - console.debug(options); - - // link galaxy modal or create one - var galaxy = parent.Galaxy; - if (galaxy && galaxy.modal) { - this.modal = galaxy.modal; - } else { - this.modal = new Ui.Modal.View(); + var self = this; + this.job_handler = new ToolJobs(this); + this.buttons = { + execute : new Ui.Button({ + icon : 'fa-check', + tooltip : 'Execute: ' + options.name, + title : 'Execute', + cls : 'btn btn-primary', + floating : 'clear', + onclick : function() { + self.job_handler.submit(); + } + }) } - - // check if the user is an admin - if (galaxy && galaxy.currUser) { - this.is_admin = galaxy.currUser.get('is_admin') - } else { - this.is_admin = false; - } - - // link options - this.options = options; - - // create deferred processing queue handler - // this handler reduces the number of requests to the api by filtering redundant requests - this.deferred = new Deferred(); - - // set element - this.setElement('<div/>'); - - // add to main element - $(this.container).append(this.$el); - - // build this form - this._buildForm(); - }, - - /** Shows the final message (usually upon successful job submission) - */ - reciept: function($el) { - $(this.container).empty(); - $(this.container).append($el); - }, - - /** Highlight and scroll to input element (currently only used for error notifications) - */ - highlight: function (input_id, message, silent) { - // get input field - var input_element = this.element_list[input_id]; - - // check input element - if (input_element) { - // mark error - input_element.error(message || 'Please verify this parameter.'); - - // scroll to first input element - if (!silent) { - $(this.container).animate({ - scrollTop: input_element.$el.offset().top - 20 - }, 500); - } - } + ToolFormBase.prototype.initialize.call(this, options); }, /** Builds a new model through api call and recreates the entire form @@ -250,256 +199,6 @@ console.debug(response); } }); - }, - - /** Main tool form build function. This function is called once a new model is available. - */ - _buildForm: function() { - // link this - var self = this; - - // reset events - this.off('refresh'); - this.off('reset'); - - // reset field list, which contains the input field elements - this.field_list = {}; - - // reset sequential input definition list, which contains the input definitions as provided from the api - this.input_list = {}; - - // reset input element list, which contains the dom elements of each input element (includes also the input field) - this.element_list = {}; - - // creates a tree/json data structure from the input form - this.tree = new ToolTree(this); - - // creates the job handler - this.job_handler = new ToolJobs(this); - - // request history content and build form - this.content = new ToolContent(this); - - // link model options - var options = this.options; - - // create ui elements - this._renderForm(options); - - // rebuild the underlying data structure - this.tree.finalize(); - - // show errors - if (options.errors) { - var error_messages = this.tree.matchResponse(options.errors); - for (var input_id in error_messages) { - this.highlight(input_id, error_messages[input_id], true); - } - } - - // add refresh listener - this.on('refresh', function() { - // by using/reseting the deferred ajax queue the number of redundant calls is reduced - self.deferred.reset(); - self.deferred.execute(function(){self._updateModel()}); - }); - - // add reset listener - this.on('reset', function() { - for (var i in this.element_list) { - this.element_list[i].reset(); - } - }); - }, - - /** Renders the UI elements required for the form - */ - _renderForm: function(options) { - // link this - var self = this; - - // create message view - this.message = new Ui.Message(); - - // button for version selection - var requirements_button = new Ui.ButtonIcon({ - icon : 'fa-info-circle', - title : 'Requirements', - tooltip : 'Display tool requirements', - onclick : function() { - if (!this.visible) { - this.visible = true; - self.message.update({ - persistent : true, - message : ToolTemplate.requirements(options), - status : 'info' - }); - } else { - this.visible = false; - self.message.update({ - message : '' - }); - } - } - }); - if (!options.requirements || options.requirements.length == 0) { - requirements_button.$el.hide(); - } - - // button for version selection - var versions_button = new Ui.ButtonMenu({ - icon : 'fa-cubes', - title : 'Versions', - tooltip : 'Select another tool version' - }); - if (options.versions && options.versions.length > 1) { - for (var i in options.versions) { - var version = options.versions[i]; - if (version != options.version) { - versions_button.addMenu({ - title : 'Switch to ' + version, - version : version, - icon : 'fa-cube', - onclick : function() { - // here we update the tool version (some tools encode the version also in the id) - options.id = options.id.replace(options.version, this.version); - options.version = this.version; - - // rebuild the model and form - self.deferred.reset(); - self.deferred.execute(function(){self._buildModel()}); - } - }); - } - } - } else { - versions_button.$el.hide(); - } - - // button menu - var menu_button = new Ui.ButtonMenu({ - icon : 'fa-caret-down', - title : 'Options', - tooltip : 'View available options' - }); - - // configure button selection - if(options.biostar_url) { - // add question option - menu_button.addMenu({ - icon : 'fa-question-circle', - title : 'Question?', - tooltip : 'Ask a question about this tool (Biostar)', - onclick : function() { - window.open(options.biostar_url + '/p/new/post/'); - } - }); - - // create search button - menu_button.addMenu({ - icon : 'fa-search', - title : 'Search', - tooltip : 'Search help for this tool (Biostar)', - onclick : function() { - window.open(options.biostar_url + '/t/' + options.id + '/'); - } - }); - }; - - // create share button - menu_button.addMenu({ - icon : 'fa-share', - title : 'Share', - tooltip : 'Share this tool', - onclick : function() { - prompt('Copy to clipboard: Ctrl+C, Enter', window.location.origin + galaxy_config.root + 'root?tool_id=' + options.id); - } - }); - - // add admin operations - if (this.is_admin) { - // create download button - menu_button.addMenu({ - icon : 'fa-download', - title : 'Download', - tooltip : 'Download this tool', - onclick : function() { - window.location.href = galaxy_config.root + 'api/tools/' + options.id + '/download'; - } - }); - } - - // create tool form section - this.section = new ToolSection.View(self, { - inputs : options.inputs, - cls : 'ui-table-plain' - }); - - // switch to classic tool form mako if the form definition is incompatible - if (this.incompatible) { - this.$el.hide(); - $('#tool-form-classic').show(); - return; - } - - // create portlet - this.portlet = new Portlet.View({ - icon : 'fa-wrench', - title : '<b>' + options.name + '</b> ' + options.description + ' (Galaxy Tool Version ' + options.version + ')', - cls : 'ui-portlet-slim', - operations: { - requirements : requirements_button, - menu : menu_button, - versions : versions_button - }, - buttons: { - execute : new Ui.Button({ - icon : 'fa-check', - tooltip : 'Execute: ' + options.name, - title : 'Execute', - cls : 'btn btn-primary', - floating : 'clear', - onclick : function() { - self.job_handler.submit(); - } - }) - } - }); - - // append message - this.portlet.append(this.message.$el, true); - - // append tool section - this.portlet.append(this.section.$el); - - // start form - this.$el.empty(); - this.$el.append(this.portlet.$el); - - // append help - if (options.help != '') { - this.$el.append(ToolTemplate.help(options.help)); - } - - // append citations - if (options.citations) { - var $citations = $('<div/>'); - var citations = new CitationModel.ToolCitationCollection(); - citations.tool_id = options.id; - var citation_list_view = new CitationView.CitationListView({ el: $citations, collection: citations } ); - citation_list_view.render(); - citations.fetch(); - this.$el.append($citations); - } - - // show message if available in model - if (options.message) { - this.message.update({ - persistent : true, - status : 'warning', - message : options.message - }); - } } }); diff -r 23fddbbd5153ba3d6c8bbbbd33fcb5aa9225f35e -r 85e8fb54b2851454a000cab23d57ce102af6aa30 client/galaxy/scripts/mvc/tools/tools-jobs.js --- a/client/galaxy/scripts/mvc/tools/tools-jobs.js +++ b/client/galaxy/scripts/mvc/tools/tools-jobs.js @@ -98,7 +98,7 @@ } // validate non-optional fields - if (!input_def.optional && input_field.validate && !input_field.validate()) { + if (!input_def.optional && input_value == null) { this.app.highlight(input_id); return false; } diff -r 23fddbbd5153ba3d6c8bbbbd33fcb5aa9225f35e -r 85e8fb54b2851454a000cab23d57ce102af6aa30 client/galaxy/scripts/mvc/tools/tools-section.js --- a/client/galaxy/scripts/mvc/tools/tools-section.js +++ b/client/galaxy/scripts/mvc/tools/tools-section.js @@ -382,6 +382,11 @@ /** Data input field */ _fieldData : function(input_def) { + if (this.app.workflow) { + var extensions = Utils.textify(input_def.extensions.toString()); + input_def.info = 'Data input \'' + input_def.name + '\' (' + extensions + ')'; + return this._fieldHidden(input_def); + } var self = this; return new SelectContent.View(this.app, { id : 'field-' + input_def.id, @@ -398,6 +403,14 @@ /** Select/Checkbox/Radio options field */ _fieldSelect : function (input_def) { + // show text field in workflow + if (this.app.workflow && input_def.is_dynamic) { + if (!Utils.validate(input_def.value)) { + input_def.value = ''; + } + return this._fieldText(input_def); + } + // configure options fields var options = []; for (var i in input_def.options) { @@ -475,7 +488,8 @@ */ _fieldHidden : function(input_def) { return new Ui.Hidden({ - id : 'field-' + input_def.id + id : 'field-' + input_def.id, + info : input_def.info }); }, diff -r 23fddbbd5153ba3d6c8bbbbd33fcb5aa9225f35e -r 85e8fb54b2851454a000cab23d57ce102af6aa30 client/galaxy/scripts/mvc/tools/tools-select-content.js --- a/client/galaxy/scripts/mvc/tools/tools-select-content.js +++ b/client/galaxy/scripts/mvc/tools/tools-select-content.js @@ -36,14 +36,7 @@ this.list = {}; // error messages - var extensions = options.extensions.toString(); - if (extensions) { - extensions = extensions.replace(/,/g, ', '); - var pos = extensions.lastIndexOf(', '); - if (pos != -1) { - extensions = extensions.substr(0, pos) + ' or ' + extensions.substr(pos+1); - } - } + var extensions = Utils.textify(options.extensions); var hda_error = 'No dataset available.'; if (extensions) { hda_error = 'No ' + extensions + ' dataset available.'; diff -r 23fddbbd5153ba3d6c8bbbbd33fcb5aa9225f35e -r 85e8fb54b2851454a000cab23d57ce102af6aa30 client/galaxy/scripts/mvc/tools/tools-tree.js --- a/client/galaxy/scripts/mvc/tools/tools-tree.js +++ b/client/galaxy/scripts/mvc/tools/tools-tree.js @@ -2,7 +2,7 @@ This class maps the tool form dom to an api compatible javascript dictionary. */ // dependencies -define([], function() { +define(['utils/utils'], function(Utils) { // tool form tree return Backbone.Model.extend({ @@ -120,7 +120,7 @@ // handle default value if (!field.skip) { - if (input.optional && field.validate && !field.validate()) { + if (field.validate && !field.validate(value)) { value = null; } add (job_input_id, input.id, value); diff -r 23fddbbd5153ba3d6c8bbbbd33fcb5aa9225f35e -r 85e8fb54b2851454a000cab23d57ce102af6aa30 client/galaxy/scripts/mvc/ui/ui-misc.js --- a/client/galaxy/scripts/mvc/ui/ui-misc.js +++ b/client/galaxy/scripts/mvc/ui/ui-misc.js @@ -412,7 +412,7 @@ initialize : function(options) { // configure options this.options = options; - + // create new element this.setElement(this._template(this.options)); @@ -425,14 +425,20 @@ // value value : function (new_val) { if (new_val !== undefined) { - this.$el.val(new_val); + this.$('hidden').val(new_val); } - return this.$el.val(); + return this.$('hidden').val(); }, // element _template: function(options) { - return '<hidden id="' + options.id + '" value="' + options.value + '"/>'; + var tmpl = '<div id="' + options.id + '" >'; + if (options.info) { + tmpl += '<label>' + options.info + '</label>'; + } + tmpl += '<hidden value="' + options.value + '"/>' + + '</div>'; + return tmpl; } }); diff -r 23fddbbd5153ba3d6c8bbbbd33fcb5aa9225f35e -r 85e8fb54b2851454a000cab23d57ce102af6aa30 client/galaxy/scripts/mvc/ui/ui-options.js --- a/client/galaxy/scripts/mvc/ui/ui-options.js +++ b/client/galaxy/scripts/mvc/ui/ui-options.js @@ -137,16 +137,7 @@ /** Validate the selected option/options */ validate: function() { - var current = this.value(); - if (!(current instanceof Array)) { - current = [current]; - } - for (var i in current) { - if ([null, 'null', undefined].indexOf(current[i]) > -1) { - return false; - } - } - return true; + return Utils.validate(this.value()); }, /** Wait message during request processing diff -r 23fddbbd5153ba3d6c8bbbbd33fcb5aa9225f35e -r 85e8fb54b2851454a000cab23d57ce102af6aa30 client/galaxy/scripts/mvc/ui/ui-select-default.js --- a/client/galaxy/scripts/mvc/ui/ui-select-default.js +++ b/client/galaxy/scripts/mvc/ui/ui-select-default.js @@ -96,16 +96,7 @@ /** Validate the current selection */ validate: function() { - var current = this.value(); - if (!(current instanceof Array)) { - current = [current]; - } - for (var i in current) { - if ([null, 'null', undefined].indexOf(current[i]) > -1) { - return false; - } - } - return true; + return Utils.validate(this.value()); }, /** Return the label/text of the current selection diff -r 23fddbbd5153ba3d6c8bbbbd33fcb5aa9225f35e -r 85e8fb54b2851454a000cab23d57ce102af6aa30 client/galaxy/scripts/mvc/upload/upload-view.js --- a/client/galaxy/scripts/mvc/upload/upload-view.js +++ b/client/galaxy/scripts/mvc/upload/upload-view.js @@ -104,7 +104,7 @@ }); // define location - $('#left .unified-panel-header-inner').append((new UploadButton.View(this.ui_button)).$el); + $('.with-upload-button').append((new UploadButton.View(this.ui_button)).$el); // load extension var self = this; diff -r 23fddbbd5153ba3d6c8bbbbd33fcb5aa9225f35e -r 85e8fb54b2851454a000cab23d57ce102af6aa30 client/galaxy/scripts/utils/utils.js --- a/client/galaxy/scripts/utils/utils.js +++ b/client/galaxy/scripts/utils/utils.js @@ -15,6 +15,40 @@ }; /** + * Validate atomic values or list of values + * usually used for selectable options + * @param{String} value - Value or list to be validated + */ +function validate (value) { + if (!(value instanceof Array)) { + value = [value]; + } + for (var i in value) { + if (['None', null, 'null', undefined, 'undefined'].indexOf(value[i]) > -1) { + return false; + } + } + return true; +}; + +/** + * Convert list to pretty string + * @param{String} lst - List of strings to be converted in human readable list sentence + */ +function textify(lst) { + var lst = lst.toString(); + if (lst) { + lst = lst.replace(/,/g, ', '); + var pos = lst.lastIndexOf(', '); + if (pos != -1) { + lst = lst.substr(0, pos) + ' or ' + lst.substr(pos+1); + } + return lst; + } + return ''; +}; + +/** * Request handler for GET * @param{String} url - Url request is made to * @param{Function} success - Callback on success @@ -212,7 +246,9 @@ time: time, wrap: wrap, request: request, - sanitize: sanitize + sanitize: sanitize, + textify: textify, + validate: validate }; }); diff -r 23fddbbd5153ba3d6c8bbbbd33fcb5aa9225f35e -r 85e8fb54b2851454a000cab23d57ce102af6aa30 config/datatypes_conf.xml.sample --- a/config/datatypes_conf.xml.sample +++ b/config/datatypes_conf.xml.sample @@ -181,7 +181,8 @@ <datatype extension="taxonomy" type="galaxy.datatypes.tabular:Taxonomy" display_in_upload="true"/><datatype extension="tabular" type="galaxy.datatypes.tabular:Tabular" display_in_upload="true" description="Any data in tab delimited format (tabular)." description_url="https://wiki.galaxyproject.org/Learn/Datatypes#Tabular_.28tab_delimited.29"/><datatype extension="twobit" type="galaxy.datatypes.binary:TwoBit" mimetype="application/octet-stream" display_in_upload="true"/> - <datatype extension="sqlite" type="galaxy.datatypes.binary:SQlite" mimetype="application/octet-stream" display_in_upload="true"/> + <datatype extension="sqlite" type="galaxy.datatypes.binary:SQlite" mimetype="application/octet-stream" display_in_upload="true"/> + <datatype extension="gemini.sqlite" type="galaxy.datatypes.binary:GeminiSQLite" mimetype="application/octet-stream" display_in_upload="True" /><datatype extension="txt" type="galaxy.datatypes.data:Text" display_in_upload="true" description="Any text file." description_url="https://wiki.galaxyproject.org/Learn/Datatypes#Plain_text"/><datatype extension="linecount" type="galaxy.datatypes.data:LineCount" display_in_upload="false"/><datatype extension="memexml" type="galaxy.datatypes.xml:MEMEXml" mimetype="application/xml" display_in_upload="true"/> @@ -271,6 +272,7 @@ --><sniffer type="galaxy.datatypes.tabular:Vcf"/><sniffer type="galaxy.datatypes.binary:TwoBit"/> + <sniffer type="galaxy.datatypes.binary:GeminiSQLite"/><sniffer type="galaxy.datatypes.binary:SQlite"/><sniffer type="galaxy.datatypes.binary:Bam"/><sniffer type="galaxy.datatypes.binary:Sff"/> diff -r 23fddbbd5153ba3d6c8bbbbd33fcb5aa9225f35e -r 85e8fb54b2851454a000cab23d57ce102af6aa30 lib/galaxy/datatypes/binary.py --- a/lib/galaxy/datatypes/binary.py +++ b/lib/galaxy/datatypes/binary.py @@ -20,7 +20,7 @@ from bx.seq.twobit import TWOBIT_MAGIC_NUMBER, TWOBIT_MAGIC_NUMBER_SWAP, TWOBIT_MAGIC_SIZE from galaxy.util import sqlite -from galaxy.datatypes.metadata import MetadataElement,ListParameter,DictParameter +from galaxy.datatypes.metadata import MetadataElement, MetadataParameter, ListParameter, DictParameter from galaxy.datatypes import metadata import dataproviders @@ -640,8 +640,66 @@ return dataproviders.dataset.SQliteDataDictProvider( dataset_source, **settings ) +#Binary.register_sniffable_binary_format("sqlite", "sqlite", SQlite) + + +class GeminiSQLite( SQlite ): + """Class describing a Gemini Sqlite database """ + MetadataElement( name="gemini_version", default='0.10.0' , param=MetadataParameter, desc="Gemini Version", + readonly=True, visible=True, no_value='0.10.0' ) + file_ext = "gemini.sqlite" + + def set_meta( self, dataset, overwrite = True, **kwd ): + super( GeminiSQLite, self ).set_meta( dataset, overwrite = overwrite, **kwd ) + try: + conn = sqlite.connect( dataset.file_name ) + c = conn.cursor() + tables_query = "SELECT version FROM version" + result = c.execute( tables_query ).fetchall() + for version, in result: + dataset.metadata.gemini_version = version + # TODO: Can/should we detect even more attributes, such as use of PED file, what was input annotation type, etc. + except Exception, e: + log.warn( '%s, set_meta Exception: %s', self, e ) + + def sniff( self, filename ): + if super( GeminiSQLite, self ).sniff( filename ): + gemini_table_names = [ "gene_detailed", "gene_summary", "resources", "sample_genotype_counts", "sample_genotypes", "samples", + "variant_impacts", "variants", "version" ] + try: + conn = sqlite.connect( filename ) + c = conn.cursor() + tables_query = "SELECT name FROM sqlite_master WHERE type='table' ORDER BY name" + result = c.execute( tables_query ).fetchall() + result = map( lambda x: x[0], result ) + for table_name in gemini_table_names: + if table_name not in result: + return False + return True + except Exception, e: + log.warn( '%s, sniff Exception: %s', self, e ) + return False + + def set_peek( self, dataset, is_multi_byte=False ): + if not dataset.dataset.purged: + dataset.peek = "Gemini SQLite Database, version %s" % ( dataset.metadata.gemini_version or 'unknown' ) + dataset.blurb = data.nice_size( dataset.get_size() ) + else: + dataset.peek = 'file does not exist' + dataset.blurb = 'file purged from disk' + + def display_peek( self, dataset ): + try: + return dataset.peek + except: + return "Gemini SQLite Database, version %s" % ( dataset.metadata.gemini_version or 'unknown' ) + +Binary.register_sniffable_binary_format( "gemini.sqlite", "gemini.sqlite", GeminiSQLite ) +# FIXME: We need to register gemini.sqlite before sqlite, since register_sniffable_binary_format and is_sniffable_binary called in upload.py +# ignores sniff order declared in datatypes_conf.xml Binary.register_sniffable_binary_format("sqlite", "sqlite", SQlite) + class Xlsx(Binary): """Class for Excel 2007 (xlsx) files""" file_ext="xlsx" diff -r 23fddbbd5153ba3d6c8bbbbd33fcb5aa9225f35e -r 85e8fb54b2851454a000cab23d57ce102af6aa30 lib/galaxy/tools/__init__.py --- a/lib/galaxy/tools/__init__.py +++ b/lib/galaxy/tools/__init__.py @@ -33,7 +33,7 @@ from galaxy.tools.actions.data_source import DataSourceToolAction from galaxy.tools.actions.data_manager import DataManagerToolAction from galaxy.tools.deps import build_dependency_manager -from galaxy.tools.parameters import check_param, params_from_strings, params_to_strings +from galaxy.tools.parameters import params_to_incoming, check_param, params_from_strings, params_to_strings from galaxy.tools.parameters import output_collect from galaxy.tools.parameters.basic import (BaseURLToolParameter, DataToolParameter, HiddenToolParameter, LibraryDatasetToolParameter, @@ -1843,7 +1843,8 @@ """Return a list of commands to be run to populate the current environment to include this tools requirements.""" return self.app.toolbox.dependency_manager.dependency_shell_commands( self.requirements, - installed_tool_dependencies=self.installed_tool_dependencies + installed_tool_dependencies=self.installed_tool_dependencies, + tool_dir=self.tool_dir, ) @property @@ -2072,6 +2073,336 @@ return tool_dict + def to_json (self, trans, **kwd): + """ + Recursively creates a tool dictionary containing repeats, dynamic options and updated states. + """ + job_id = kwd.get('job_id', None) + dataset_id = kwd.get('dataset_id', None) + + # load job details if provided + job = None + if job_id: + try: + job_id = trans.security.decode_id( job_id ) + job = trans.sa_session.query( trans.app.model.Job ).get( job_id ) + except Exception, exception: + trans.response.status = 500 + log.error('Failed to retrieve job.') + return { 'error': 'Failed to retrieve job.' } + elif dataset_id: + try: + dataset_id = trans.security.decode_id( dataset_id ) + data = trans.sa_session.query( trans.app.model.HistoryDatasetAssociation ).get( dataset_id ) + if not ( trans.user_is_admin() or trans.app.security_agent.can_access_dataset( trans.get_current_user_roles(), data.dataset ) ): + trans.response.status = 500 + log.error('User has no access to dataset.') + return { 'error': 'User has no access to dataset.' } + job = data.creating_job + if not job: + trans.response.status = 500 + log.error('Creating job not found.') + return { 'error': 'Creating job not found.' } + except Exception, exception: + trans.response.status = 500 + log.error('Failed to get job information.') + return { 'error': 'Failed to get job information.' } + + # load job parameters into incoming + tool_message = '' + if job: + try: + job_params = job.get_param_values( trans.app, ignore_errors = True ) + job_messages = self.check_and_update_param_values( job_params, trans, update_values=False ) + tool_message = self._compare_tool_version(trans, job) + params_to_incoming( kwd, self.inputs, job_params, trans.app ) + except Exception, exception: + trans.response.status = 500 + return { 'error': str( exception ) } + + # create parameter object + params = galaxy.util.Params( kwd, sanitize = False ) + + # convert value to jsonifiable value + def jsonify(v): + # check if value is numeric + isnumber = False + try: + float(v) + isnumber = True + except Exception: + pass + + # fix hda parsing + if isinstance(v, trans.app.model.HistoryDatasetAssociation): + return { + 'id' : trans.security.encode_id(v.id), + 'src' : 'hda' + } + elif isinstance(v, bool): + if v is True: + return 'true' + else: + return 'false' + elif isinstance(v, basestring) or isnumber: + return v + else: + return None + + # ensures that input dictionary is jsonifiable + def sanitize(dict): + # get current value + value = dict['value'] if 'value' in dict else None + + # jsonify by type + if dict['type'] in ['data']: + if isinstance(value, list): + value = [ jsonify(v) for v in value ] + else: + value = [ jsonify(value) ] + value = { 'values': value } + elif isinstance(value, list): + value = [ jsonify(v) for v in value ] + else: + value = jsonify(value) + + # update and return + dict['value'] = value + return dict + + # initialize state using default parameters + def initialize_state(trans, inputs, state, context=None): + context = ExpressionContext(state, context) + for input in inputs.itervalues(): + state[input.name] = input.get_initial_value(trans, context) + + # check the current state of a value and update it if necessary + def check_state(trans, input, default_value, context): + value = default_value + error = 'State validation failed.' + try: + # resolves the inconsistent definition of boolean parameters (see base.py) without modifying shared code + if input.type == 'boolean' and isinstance(default_value, basestring): + value, error = [util.string_as_bool(default_value), None] + else: + value, error = check_param(trans, input, default_value, context) + except Exception, err: + log.error('Checking parameter failed. %s', str(err)) + pass + return [value, error] + + # populates state with incoming url parameters + def populate_state(trans, inputs, state, errors, incoming, prefix="", context=None ): + context = ExpressionContext(state, context) + for input in inputs.itervalues(): + key = prefix + input.name + if input.type == 'repeat': + group_state = state[input.name] + rep_index = 0 + del group_state[:] + while True: + rep_name = "%s_%d" % (key, rep_index) + if not any([incoming_key.startswith(rep_name) for incoming_key in incoming.keys()]): + break + if rep_index < input.max: + new_state = {} + new_state['__index__'] = rep_index + initialize_state(trans, input.inputs, new_state, context) + group_state.append(new_state) + populate_state(trans, input.inputs, new_state, errors, incoming, prefix=rep_name + "|", context=context) + rep_index += 1 + elif input.type == 'conditional': + group_state = state[input.name] + group_prefix = "%s|" % ( key ) + test_param_key = group_prefix + input.test_param.name + default_value = incoming.get(test_param_key, group_state.get(input.test_param.name, None)) + value, error = check_state(trans, input.test_param, default_value, context) + if error: + errors[test_param_key] = error + else: + try: + current_case = input.get_current_case(value, trans) + group_state = state[input.name] = {} + initialize_state(trans, input.cases[current_case].inputs, group_state, context) + populate_state( trans, input.cases[current_case].inputs, group_state, errors, incoming, prefix=group_prefix, context=context) + group_state['__current_case__'] = current_case + except Exception, e: + errors[test_param_key] = 'The selected case is unavailable/invalid.' + pass + group_state[input.test_param.name] = value + else: + default_value = incoming.get(key, state.get(input.name, None)) + value, error = check_state(trans, input, default_value, context) + if error: + errors[key] = error + state[input.name] = value + + # builds tool model including all attributes + def iterate(group_inputs, inputs, state_inputs, other_values=None): + other_values = ExpressionContext( state_inputs, other_values ) + for input_index, input in enumerate( inputs.itervalues() ): + # create model dictionary + tool_dict = input.to_dict(trans) + if tool_dict is None: + continue + + # state for subsection/group + group_state = state_inputs[input.name] + + # iterate and update values + if input.type == 'repeat': + group_cache = tool_dict['cache'] = {} + for i in range( len( group_state ) ): + group_cache[i] = {} + iterate( group_cache[i], input.inputs, group_state[i], other_values ) + elif input.type == 'conditional': + if 'test_param' in tool_dict: + test_param = tool_dict['test_param'] + test_param['value'] = jsonify(group_state[test_param['name']]) + if '__current_case__' in group_state: + i = group_state['__current_case__'] + iterate(tool_dict['cases'][i]['inputs'], input.cases[i].inputs, group_state, other_values) + else: + # create input dictionary, try to pass other_values if to_dict function supports it e.g. dynamic options + try: + tool_dict = input.to_dict(trans, other_values=other_values) + except Exception: + pass + + # identify name + input_name = tool_dict.get('name') + if input_name: + # update input value from tool state + if input_name in state_inputs: + tool_dict['value'] = state_inputs[input_name] + + # sanitize if value exists + tool_dict = sanitize(tool_dict) + + # backup final input dictionary + group_inputs[input_index] = tool_dict + + # sanatization for the final tool state + def sanitize_state(state): + keys = None + if isinstance(state, dict): + keys = state + elif isinstance(state, list): + keys = range( len(state) ) + if keys: + for k in keys: + if isinstance(state[k], dict) or isinstance(state[k], list): + sanitize_state(state[k]) + else: + state[k] = jsonify(state[k]) + + # do param translation here, used by datasource tools + if self.input_translator: + self.input_translator.translate( params ) + + # initialize and populate tool state + state_inputs = {} + state_errors = {} + initialize_state(trans, self.inputs, state_inputs) + populate_state(trans, self.inputs, state_inputs, state_errors, params.__dict__) + + # create basic tool model + tool_model = self.to_dict(trans) + tool_model['inputs'] = {} + + # build tool model and tool state + iterate(tool_model['inputs'], self.inputs, state_inputs, '') + + # sanitize tool state + sanitize_state(state_inputs) + + # load tool help + tool_help = '' + if self.help: + tool_help = self.help + tool_help = tool_help.render( static_path=url_for( '/static' ), host_url=url_for('/', qualified=True) ) + if type( tool_help ) is not unicode: + tool_help = unicode( tool_help, 'utf-8') + + # check if citations exist + tool_citations = False + if self.citations: + tool_citations = True + + # get tool versions + tool_versions = [] + tools = self.app.toolbox.get_loaded_tools_by_lineage(self.id) + for t in tools: + tool_versions.append(t.version) + + ## add information with underlying requirements and their versions + tool_requirements = [] + if self.requirements: + for requirement in self.requirements: + tool_requirements.append({ + 'name' : requirement.name, + 'version' : requirement.version + }) + + # add additional properties + tool_model.update({ + 'help' : tool_help, + 'citations' : tool_citations, + 'biostar_url' : trans.app.config.biostar_url, + 'message' : tool_message, + 'versions' : tool_versions, + 'requirements' : tool_requirements, + 'errors' : state_errors, + 'state_inputs' : state_inputs + }) + + # check for errors + if 'error' in tool_message: + return tool_message + + # return enriched tool model + return tool_model + + def _compare_tool_version( self, trans, job ): + """ + Compares a tool version with the tool version from a job (from ToolRunner). + """ + tool_id = job.tool_id + tool_version = job.tool_version + message = '' + try: + select_field, tools, tool = self.app.toolbox.get_tool_components( tool_id, tool_version=tool_version, get_loaded_tools_by_lineage=False, set_selected=True ) + if tool is None: + trans.response.status = 500 + return { 'error': 'This dataset was created by an obsolete tool (%s). Can\'t re-run.' % tool_id } + if ( self.id != tool_id and self.old_id != tool_id ) or self.version != tool_version: + if self.id == tool_id: + if tool_version == None: + # for some reason jobs don't always keep track of the tool version. + message = '' + else: + message = 'This job was initially run with tool version "%s", which is currently not available. ' % tool_version + if len( tools ) > 1: + message += 'You can re-run the job with the selected tool or choose another derivation of the tool.' + else: + message += 'You can re-run the job with this tool version, which is a derivation of the original tool.' + else: + if len( tools ) > 1: + message = 'This job was initially run with tool version "%s", which is currently not available. ' % tool_version + message += 'You can re-run the job with the selected tool or choose another derivation of the tool.' + else: + message = 'This job was initially run with tool id "%s", version "%s", which is ' % ( tool_id, tool_version ) + message += 'currently not available. You can re-run the job with this tool, which is a derivation of the original tool.' + except Exception, error: + trans.response.status = 500 + return { 'error': str (error) } + + # can't rerun upload, external data sources, et cetera. workflow compatible will proxy this for now + #if not self.is_workflow_compatible: + # trans.response.status = 500 + # return { 'error': 'The \'%s\' tool does currently not support re-running.' % self.name } + return message + def get_default_history_by_trans( self, trans, create=False ): return trans.get_history( create=create ) diff -r 23fddbbd5153ba3d6c8bbbbd33fcb5aa9225f35e -r 85e8fb54b2851454a000cab23d57ce102af6aa30 lib/galaxy/tools/deps/brew_exts.py --- a/lib/galaxy/tools/deps/brew_exts.py +++ b/lib/galaxy/tools/deps/brew_exts.py @@ -28,6 +28,7 @@ import os import re import sys +import string import subprocess WHITESPACE_PATTERN = re.compile("[\s]+") @@ -43,6 +44,7 @@ CANNOT_DETERMINE_TAP_ERROR_MESSAGE = "Cannot determine tap of specified recipe - please use fully qualified recipe (e.g. homebrew/science/samtools)." VERBOSE = False RELAXED = False +BREW_ARGS = [] class BrewContext(object): @@ -104,6 +106,7 @@ def main(): global VERBOSE global RELAXED + global BREW_ARGS parser = argparse.ArgumentParser(description=DESCRIPTION) parser.add_argument("--brew", help="Path to linuxbrew 'brew' executable to target") actions = ["vinstall", "vuninstall", "vdeps", "vinfo", "env"] @@ -114,11 +117,13 @@ parser.add_argument('version', metavar='version', help="Version for action (e.g. 0.1.19).") parser.add_argument('--relaxed', action='store_true', help="Relaxed processing - for instance allow use of env on non-vinstall-ed recipes.") parser.add_argument('--verbose', action='store_true', help="Verbose output") + parser.add_argument('restargs', nargs=argparse.REMAINDER) args = parser.parse_args() if args.verbose: VERBOSE = True if args.relaxed: RELAXED = True + BREW_ARGS = args.restargs if not action: action = args.action brew_context = BrewContext(args) @@ -159,7 +164,7 @@ return self.message -def versioned_install(recipe_context, package=None, version=None): +def versioned_install(recipe_context, package=None, version=None, installed_deps=[]): if package is None: package = recipe_context.recipe version = recipe_context.version @@ -176,10 +181,15 @@ versioned = version_info[2] if versioned: dep_to_version[dep] = dep_version + if dep in installed_deps: + continue versioned_install(recipe_context, dep, dep_version) + installed_deps.append(dep) else: # Install latest. dep_to_version[dep] = None + if dep in installed_deps: + continue unversioned_install(dep) try: for dep in deps: @@ -198,7 +208,16 @@ } deps_metadata.append(dep_metadata) - brew_execute(["install", package]) + cellar_root = recipe_context.brew_context.homebrew_cellar + cellar_path = recipe_context.cellar_path + env_actions = build_env_actions(deps_metadata, cellar_root, cellar_path, custom_only=True) + env = EnvAction.build_env(env_actions) + args = ["install"] + if VERBOSE: + args.append("--verbose") + args.extend(BREW_ARGS) + args.append(package) + brew_execute(args, env=env) deps = brew_execute(["deps", package]) deps = [d.strip() for d in deps.split("\n") if d] metadata = { @@ -278,10 +297,10 @@ pass -def brew_execute(args): +def brew_execute(args, env=None): os.environ["HOMEBREW_NO_EMOJI"] = "1" # simplify brew parsing. cmds = ["brew"] + args - return execute(cmds) + return execute(cmds, env=env) def build_env_statements_from_recipe_context(recipe_context, **kwds): @@ -290,11 +309,20 @@ return env_statements -def build_env_statements(cellar_root, cellar_path, relaxed=None): +def build_env_statements(cellar_root, cellar_path, relaxed=None, custom_only=False): deps = load_versioned_deps(cellar_path, relaxed=relaxed) + actions = build_env_actions(deps, cellar_root, cellar_path, relaxed, custom_only) + env_statements = [] + for action in actions: + env_statements.extend(action.to_statements()) + return "\n".join(env_statements) + + +def build_env_actions(deps, cellar_root, cellar_path, relaxed=None, custom_only=False): path_appends = [] ld_path_appends = [] + actions = [] def handle_keg(cellar_path): bin_path = os.path.join(cellar_path, "bin") @@ -303,6 +331,14 @@ lib_path = os.path.join(cellar_path, "lib") if os.path.isdir(lib_path): ld_path_appends.append(lib_path) + env_path = os.path.join(cellar_path, "platform_environment.json") + if os.path.exists(env_path): + with open(env_path, "r") as f: + env_metadata = json.load(f) + if "actions" in env_metadata: + def to_action(desc): + return EnvAction(cellar_path, desc) + actions.extend(map(to_action, env_metadata["actions"])) for dep in deps: package = dep['name'] @@ -311,14 +347,54 @@ handle_keg( dep_cellar_path ) handle_keg( cellar_path ) - env_statements = [] - if path_appends: - env_statements.append("PATH=" + ":".join(path_appends) + ":$PATH") - env_statements.append("export PATH") - if ld_path_appends: - env_statements.append("LD_LIBRARY_PATH=" + ":".join(ld_path_appends) + ":$LD_LIBRARY_PATH") - env_statements.append("export LD_LIBRARY_PATH") - return "\n".join(env_statements) + if not custom_only: + if path_appends: + actions.append(EnvAction(cellar_path, {"action": "prepend", "variable": "PATH", "value": ":".join(path_appends)})) + if ld_path_appends: + actions.append(EnvAction(cellar_path, {"action": "prepend", "variable": "LD_LIBRARY_PATH", "value": ":".join(path_appends)})) + return actions + + +class EnvAction(object): + + def __init__(self, keg_root, action_description): + self.variable = action_description["variable"] + self.action = action_description["action"] + self.value = string.Template(action_description["value"]).safe_substitute({ + 'KEG_ROOT': keg_root, + }) + + @staticmethod + def build_env(env_actions): + new_env = os.environ.copy() + map(lambda env_action: env_action.modify_environ(new_env), env_actions) + return new_env + + def modify_environ(self, environ): + if self.action == "set" or not environ.get(self.variable, ""): + environ[self.variable] = self.__eval("${value}") + elif self.action == "prepend": + environ[self.variable] = self.__eval("${value}:%s" % environ[self.variable]) + else: + environ[self.variable] = self.__eval("%s:${value}" % environ[self.variable]) + + def __eval(self, template): + return string.Template(template).safe_substitute( + variable=self.variable, + value=self.value, + ) + + def to_statements(self): + if self.action == "set": + template = '''${variable}="${value}"''' + elif self.action == "prepend": + template = '''${variable}="${value}:$$${variable}"''' + else: + template = '''${variable}="$$${variable}:${value}"''' + return [ + self.__eval(template), + "export %s" % self.variable + ] @contextlib.contextmanager @@ -350,8 +426,15 @@ return execute(cmds) -def execute(cmds): - p = subprocess.Popen(cmds, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE) +def execute(cmds, env=None): + subprocess_kwds = dict( + shell=False, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) + if env: + subprocess_kwds["env"] = env + p = subprocess.Popen(cmds, **subprocess_kwds) #log = p.stdout.read() global VERBOSE stdout, stderr = p.communicate() @@ -363,7 +446,10 @@ def brew_deps(package): - stdout = brew_execute(["deps", package]) + args = ["deps"] + args.extend(BREW_ARGS) + args.append(package) + stdout = brew_execute(args) return [p.strip() for p in stdout.split("\n") if p] diff -r 23fddbbd5153ba3d6c8bbbbd33fcb5aa9225f35e -r 85e8fb54b2851454a000cab23d57ce102af6aa30 lib/galaxy/tools/deps/resolvers/brewed_tool_shed_packages.py --- /dev/null +++ b/lib/galaxy/tools/deps/resolvers/brewed_tool_shed_packages.py @@ -0,0 +1,150 @@ +""" +This dependency resolver resolves tool shed dependencies (those defined +tool_dependencies.xml) installed using Platform Homebrew and converted +via shed2tap (e.g. https://github.com/jmchilton/homebrew-toolshed). +""" +import logging +import os +from xml.etree import ElementTree as ET + +from .resolver_mixins import ( + UsesHomebrewMixin, + UsesToolDependencyDirMixin, + UsesInstalledRepositoriesMixin, +) +from ..resolvers import DependencyResolver, INDETERMINATE_DEPENDENCY + +log = logging.getLogger(__name__) + + +class HomebrewToolShedDependencyResolver( + DependencyResolver, + UsesHomebrewMixin, + UsesToolDependencyDirMixin, + UsesInstalledRepositoriesMixin, +): + resolver_type = "tool_shed_tap" + + def __init__(self, dependency_manager, **kwds): + self._init_homebrew(**kwds) + self._init_base_path(dependency_manager, **kwds) + + def resolve(self, name, version, type, **kwds): + if type != "package": + return INDETERMINATE_DEPENDENCY + + if version is None: + return INDETERMINATE_DEPENDENCY + + return self._find_tool_dependencies(name, version, type, **kwds) + + def _find_tool_dependencies(self, name, version, type, **kwds): + installed_tool_dependency = self._get_installed_dependency(name, type, version=version, **kwds) + if installed_tool_dependency: + return self._resolve_from_installed_tool_dependency(name, version, installed_tool_dependency) + + if "tool_dir" in kwds: + tool_directory = os.path.abspath(kwds["tool_dir"]) + tool_depenedencies_path = os.path.join(tool_directory, "tool_dependencies.xml") + if os.path.exists(tool_depenedencies_path): + return self._resolve_from_tool_dependencies_path(name, version, tool_depenedencies_path) + + return INDETERMINATE_DEPENDENCY + + def _resolve_from_installed_tool_dependency(self, name, version, installed_tool_dependency): + tool_shed_repository = installed_tool_dependency.tool_shed_repository + recipe_name = build_recipe_name( + package_name=name, + package_version=version, + repository_owner=tool_shed_repository.owner, + repository_name=tool_shed_repository.name, + ) + return self._find_dep_default(recipe_name, None) + + def _resolve_from_tool_dependencies_path(self, name, version, tool_dependencies_path): + try: + raw_dependencies = RawDependencies(tool_dependencies_path) + except Exception: + log.debug("Failed to parse dependencies in file %s" % tool_dependencies_path) + return INDETERMINATE_DEPENDENCY + + raw_dependency = raw_dependencies.find(name, version) + if not raw_dependency: + return INDETERMINATE_DEPENDENCY + + recipe_name = build_recipe_name( + package_name=name, + package_version=version, + repository_owner=raw_dependency.repository_owner, + repository_name=raw_dependency.repository_name + ) + dep = self._find_dep_default(recipe_name, None) + return dep + + +class RawDependencies(object): + + def __init__(self, dependencies_file): + self.root = ET.parse(dependencies_file).getroot() + dependencies = [] + package_els = self.root.findall("package") or [] + for package_el in package_els: + repository_el = package_el.find("repository") + if repository_el is None: + continue + dependency = RawDependency(self, package_el, repository_el) + dependencies.append(dependency) + self.dependencies = dependencies + + def find(self, package_name, package_version): + target_dependency = None + + for dependency in self.dependencies: + if dependency.package_name == package_name and dependency.package_version == package_version: + target_dependency = dependency + break + return target_dependency + + +class RawDependency(object): + + def __init__(self, dependencies, package_el, repository_el): + self.dependencies = dependencies + self.package_el = package_el + self.repository_el = repository_el + + def __repr__(self): + temp = "Dependency[package_name=%s,version=%s,dependent_package=%s]" + return temp % ( + self.package_el.attrib["name"], + self.package_el.attrib["version"], + self.repository_el.attrib["name"] + ) + + @property + def repository_owner(self): + return self.repository_el.attrib["owner"] + + @property + def repository_name(self): + return self.repository_el.attrib["name"] + + @property + def package_name(self): + return self.package_el.attrib["name"] + + @property + def package_version(self): + return self.package_el.attrib["version"] + + +def build_recipe_name(package_name, package_version, repository_owner, repository_name): + # TODO: Consider baking package_name and package_version into name? (would be more "correct") + owner = repository_owner.replace("-", "") + name = repository_name + name = name.replace("_", "").replace("-", "") + base = "%s_%s" % (owner, name) + return base + + +__all__ = [HomebrewToolShedDependencyResolver] diff -r 23fddbbd5153ba3d6c8bbbbd33fcb5aa9225f35e -r 85e8fb54b2851454a000cab23d57ce102af6aa30 lib/galaxy/tools/deps/resolvers/galaxy_packages.py --- a/lib/galaxy/tools/deps/resolvers/galaxy_packages.py +++ b/lib/galaxy/tools/deps/resolvers/galaxy_packages.py @@ -1,12 +1,13 @@ -from os.path import join, islink, realpath, basename, exists, abspath +from os.path import join, islink, realpath, basename, exists from ..resolvers import DependencyResolver, INDETERMINATE_DEPENDENCY, Dependency +from .resolver_mixins import UsesToolDependencyDirMixin import logging log = logging.getLogger( __name__ ) -class GalaxyPackageDependencyResolver(DependencyResolver): +class GalaxyPackageDependencyResolver(DependencyResolver, UsesToolDependencyDirMixin): resolver_type = "galaxy_packages" def __init__(self, dependency_manager, **kwds): @@ -16,7 +17,7 @@ ## resolver that will just grab 'default' version of exact version ## unavailable. self.versionless = str(kwds.get('versionless', "false")).lower() == "true" - self.base_path = abspath( kwds.get('base_path', dependency_manager.default_base_path) ) + self._init_base_path( dependency_manager, **kwds ) def resolve( self, name, version, type, **kwds ): """ diff -r 23fddbbd5153ba3d6c8bbbbd33fcb5aa9225f35e -r 85e8fb54b2851454a000cab23d57ce102af6aa30 lib/galaxy/tools/deps/resolvers/homebrew.py --- a/lib/galaxy/tools/deps/resolvers/homebrew.py +++ b/lib/galaxy/tools/deps/resolvers/homebrew.py @@ -12,20 +12,19 @@ incompatible changes coming. """ -import os -from ..brew_exts import DEFAULT_HOMEBREW_ROOT, recipe_cellar_path, build_env_statements -from ..resolvers import DependencyResolver, INDETERMINATE_DEPENDENCY, Dependency +from .resolver_mixins import UsesHomebrewMixin +from ..resolvers import DependencyResolver, INDETERMINATE_DEPENDENCY # TODO: Implement prefer version linked... PREFER_VERSION_LINKED = 'linked' PREFER_VERSION_LATEST = 'latest' -UNKNOWN_PREFER_VERSION_MESSAGE_TEMPLATE = "HomebrewDependencyResolver prefer_version must be latest %s" +UNKNOWN_PREFER_VERSION_MESSAGE_TEMPLATE = "HomebrewDependencyResolver prefer_version must be %s" UNKNOWN_PREFER_VERSION_MESSAGE = UNKNOWN_PREFER_VERSION_MESSAGE_TEMPLATE % (PREFER_VERSION_LATEST) DEFAULT_PREFER_VERSION = PREFER_VERSION_LATEST -class HomebrewDependencyResolver(DependencyResolver): +class HomebrewDependencyResolver(DependencyResolver, UsesHomebrewMixin): resolver_type = "homebrew" def __init__(self, dependency_manager, **kwds): @@ -38,11 +37,7 @@ if self.versionless and self.prefer_version not in [PREFER_VERSION_LATEST]: raise Exception(UNKNOWN_PREFER_VERSION_MESSAGE) - cellar_root = kwds.get('cellar', None) - if cellar_root is None: - cellar_root = os.path.join(DEFAULT_HOMEBREW_ROOT, "Cellar") - - self.cellar_root = cellar_root + self._init_homebrew(**kwds) def resolve(self, name, version, type, **kwds): if type != "package": @@ -53,41 +48,6 @@ else: return self._find_dep_versioned(name, version) - def _find_dep_versioned(self, name, version): - recipe_path = recipe_cellar_path(self.cellar_root, name, version) - if not os.path.exists(recipe_path) or not os.path.isdir(recipe_path): - return INDETERMINATE_DEPENDENCY - - commands = build_env_statements(self.cellar_root, recipe_path, relaxed=True) - return HomebrewDependency(commands) - - def _find_dep_default(self, name, version): - installed_versions = self._installed_versions(name) - if not installed_versions: - return INDETERMINATE_DEPENDENCY - - # Just grab newest installed version - may make sense some day to find - # the linked version instead. - default_version = sorted(installed_versions, reverse=True)[0] - return self._find_dep_versioned(name, default_version) - - def _installed_versions(self, recipe): - recipe_base_path = os.path.join(self.cellar_root, recipe) - if not os.path.exists(recipe_base_path): - return [] - - names = os.listdir(recipe_base_path) - return filter(lambda n: os.path.isdir(os.path.join(recipe_base_path, n)), names) - - -class HomebrewDependency(Dependency): - - def __init__(self, commands): - self.commands = commands - - def shell_commands(self, requirement): - return self.commands.replace("\n", ";") + "\n" - def _string_as_bool( value ): return str( value ).lower() == "true" This diff is so big that we needed to truncate the remainder. Repository URL: https://bitbucket.org/galaxy/galaxy-central/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email.
participants (1)
-
commits-noreply@bitbucket.org