root/galaxy-taverna/workflow-to-galaxy/lib/workflow-to-galaxy/generator.rb @ 81

Revision 81, 10.7 KB (checked in by kostas.karasavvas@…, 3 years ago)

v0.2.2 -- allows multi-line input in Galaxy and properly sanitises Galaxy's newline (crcn) to \n before sending to the taverna server

Line 
1
2# Contains code to generate the Galaxy's tool xml and script files
3module Generator
4
5
6  # private methods
7  private
8
9
10  def indent(n)
11    ind = ""
12    n.times { ind += "  "  }
13    ind
14  end
15
16
17  def tool_b(out, name)
18    out.write("<tool id=\"#{name}_id\" name=\"#{name}\">\n")
19  end
20
21  def command_be(out, me_rest, script)
22    out.write indent(1) + "<command interpreter=\"ruby\">\n"
23    out.write indent(2) + script + "\n"
24    me_rest.workflow.inputs.each do |i|
25      out.write indent(2) + "#if $#{i.name}_source.history_or_textfield == \"textfield\":\n"
26      out.write indent(3) + "false \"$#{i.name}_source.textfield_#{i.name}\"\n"
27      out.write indent(2) + "#else:\n"
28      out.write indent(3) + "true  \"$#{i.name}_source.history_#{i.name}\"\n"
29      out.write indent(2) + "#end if\n"
30    end
31    out.write indent(2)
32    me_rest.workflow.outputs.each do |o|
33      out.write "$#{o.name} "
34    end
35    out.write "\n"
36    out.write indent(1) + "</command>\n"
37  end
38
39  def inputs_be(out, inputs)
40    out.write indent(1) + "<inputs>\n"
41    if inputs.size >= 1
42      inputs.each do |i|
43        out.write indent(2) + "<conditional name=\"#{i.name}_source\">\n"
44        out.write indent(3) + "<param name=\"history_or_textfield\" type=\"select\" label=\"Select source for #{i.name}\">\n"
45        out.write indent(4) + "<option value=\"history\">From history</option>\n"
46        out.write indent(4) + "<option value=\"textfield\" selected=\"true\">Type manually</option>\n"
47        out.write indent(3) + "</param>\n"
48        out.write indent(3) + "<when value=\"history\">\n"
49        out.write indent(4) + "<param name=\"history_#{i.name}\" type=\"data\" label=\"Select #{i.name}\"/>\n"
50        out.write indent(3) + "</when>\n"
51        out.write indent(3) + "<when value=\"textfield\">\n"
52        out.write indent(4) + "<param name=\"textfield_#{i.name}\" type=\"text\" area=\"True\" size=\"2x50\" "
53        if i.examples.size >= 1 
54          # escape double quotes characters and add <BR> for newlines for galaxy's xml file
55          ex = i.examples[0].to_s.gsub('"', '&quot;')
56          # convert newlines to HTML newlines to display in textareas inputs
57          ex = ex.gsub(/[\n]/, '&#xA;')
58          out.write "value=\"#{ex}\" "         
59        end
60        out.write "label=\"Enter #{i.name}\"/>\n"
61        out.write indent(3) + "</when>\n"
62        out.write indent(2) + "</conditional>\n"
63      end
64    else
65      out.write indent(2) + "<param name=\"input\" type=\"select\" display=\"radio\" size=\"250\" label=\"This workflow has no inputs\" />\n"
66    end 
67    out.write indent(1) + "</inputs>\n"
68  end
69
70  def outputs_be(out, outputs)
71    out.write indent(1) + "<outputs>\n"
72    outputs.each do |o|
73      out.write indent(2) + "<data format=\"tabular\" name=\"#{o.name}\" label=\"#{o.name}\"/>\n"
74    end
75    out.write indent(1) + "</outputs>\n"
76  end
77
78  def help_be(out, me_rest)
79    out.write indent(1) + "<help>\n"
80    out.write "**What it does**\n\n"
81
82    description = me_rest.workflow.description + "\n\n"
83
84    # Sometimes the workflow description contains HTML tags that are not allowed
85    # in Galaxy's xml interface specification and thus are removed! Same for
86    # HTML entities!
87    # TODO go through tags and find Galaxy's equivalent to include
88    description.gsub!(/<.*?>|&.*?;/, '')
89
90    # To remove ^M (cntl-v + cntl-m) characters that DOS files might have
91    description.gsub!(/\r/, '')
92   
93    # TODO that works as a literal too but font changes to courier!
94    #out.write "::\n\n"   # Start Galaxy's literal block to ignore indendation
95
96    # remove indendation from all description lines since Galaxy is confused by it
97    description.split(/[\n]/).each { |l| out.write "#{l.gsub(/^\s+/, '')}\n" }
98
99    # endline makes the following be parsed as a Galaxy GUI construct
100    out.write "\n"       
101
102    if me_rest.workflow.inputs.size >= 1
103      out.write "-----\n\n"
104      out.write "**Inputs**\n\n"
105      me_rest.workflow.inputs.each do |i|
106        out.write "- **#{i.name}** "
107        if i.descriptions.size >= 1
108          i.descriptions.each do |desc|
109            out.write desc.to_s + " "
110          end
111        end
112        if i.examples.size >= 1
113          out.write "Examples include:\n\n"
114          i.examples.each do |ex|
115            # some examples have a newline between them that breaks Galaxy's GUI
116            # so we remove it
117            out.write "  - " + ex.to_s.gsub(/[\n]/, ' ') + "\n"
118          end
119        end
120        out.write "\n"
121      end
122      out.write "\n"
123    end
124
125    # TODO this code is identical to the inputs code above -- method?
126    if me_rest.workflow.outputs.size >= 1
127      out.write "-----\n\n"
128      out.write "**Outputs**\n\n"
129      me_rest.workflow.outputs.each do |o|
130        out.write "- **#{o.name}** "
131        if o.descriptions.size >= 1
132          o.descriptions.each do |desc|
133            out.write desc.to_s + " "
134          end
135        end
136        if o.examples.size >= 1
137          out.write "Examples include:\n\n"
138          o.examples.each do |ex|
139            out.write "  - " + ex.to_s + "\n"
140          end
141        end
142        out.write "\n"
143      end
144      out.write "\n"
145    end
146
147    out.write "-----\n\n"
148    out.write ".. class:: warningmark\n\n"
149    out.write "**Please note that some workflows are not up-to-date or have dependencies** " <<
150              "that cannot be met by the specific Taverna server that you specified during " <<
151              "generation of this tool. You can make sure that the workflow is valid " <<
152              "by running it in the Taverna Workbench first to confirm that it works " <<
153              "before running it via Galaxy.\n\n"
154
155    out.write "-----\n\n"
156    out.write ".. class:: warningmark\n\n"
157    out.write "**Please note that there might be some repetitions in the workflow description** " <<
158              "in some of the generated workflows. This is due to a backwards compatibility " <<
159              "issue on the myExperiment repository which keeps the old descriptions to make " <<
160              "sure that no information is lost.\n\n"
161
162    out.write "-----\n\n"
163    out.write ".. class:: infomark\n\n"
164    out.write "**For more information on that workflow please visit** #{me_rest.uri.gsub(/(.*workflows\/\d+)[\/.].*/, '\1')}.\n\n"
165
166    out.write indent(1) + "</help>\n"
167  end
168
169  def tool_e(out)
170    out.write("</tool>\n")
171  end
172
173
174
175
176  def script_preample(out)
177    out.write("#!/usr/bin/env ruby\n\n")
178    out.write("require 'rubygems'\n")
179    out.write("require 't2-server'\n")
180    out.write("require 'open-uri'\n\n")
181  end
182
183  def script_util_methods(out)
184
185    out.write <<UTIL_METHODS
186   
187# method that flattens the list of list of list ... result of get_output
188def print_flattened_result(out, data_lists)
189  data_lists.each do |l|
190    if l.instance_of? Array
191      print_flattened_result(out, l)
192    else
193      out.puts l
194    end
195  end
196end
197
198
199# method that acquires all the results of the specified output
200def get_outputs(run, refs, outfile, dir)
201  data_lists = run.get_output(dir, refs)
202  print_flattened_result(outfile, data_lists)
203end
204
205
206#
207# Sanitize single quotes, double quotes, at symbol and newline in str. E.g. Galaxy
208# substitutes them to __sq__, __dq__, __at__ and __cr____cn__ respectively. This
209# methods turns them back to their original values before using them (i.e.
210# sending them to the taverna server
211#
212def sanitize(string)
213  string.gsub(/(__sq__|__dq__|__at__|__cr____cn__)/) do
214    if $1 == '__sq__'
215      "'"
216    elsif $1 == '__dq__'
217      '\\\"'
218    elsif $1 == '__cr____cn__'
219      '\n'
220    else
221      '@'
222    end
223  end
224end
225
226#
227# Deletes last new line of file if it exists! It is needed for t2 workflows that
228# do not sanitize properly, i.e. via a user-provided beanshell script
229#
230def chomp_last_newline(file)
231
232  if File.file?(file) and File.size(file) > 1
233    f = open(file, "rb+")
234    f.seek(-1, File::SEEK_END)
235    f.truncate(File.size(file) - 1) if f.read(1) == "\\n"
236    f.close
237  end
238
239end
240
241
242UTIL_METHODS
243
244  end
245
246
247  def script_create_t2_run(out, wkf_uri, t2_uri)
248    out.write <<CREATE_T2_RUN
249
250# use the uri reference to download the workflow locally
251wkf_file = URI.parse('#{wkf_uri}')
252in_wkf = open(wkf_file)
253wkf = in_wkf.read()
254
255# create run
256begin
257  run = T2Server::Run.create('#{t2_uri}', wkf)
258rescue T2Server::T2ServerError => e
259  exit 1
260end
261
262CREATE_T2_RUN
263
264  end
265
266
267  def script_init_inputs(out, me_rest)
268    out.write "# get input arguments -- for each input a boolean specifies if it's from history\n"
269    out.write "# thus, for each me_rest input we have two arguments in the script!\n"
270    me_rest.workflow.inputs.each_with_index do |input, i|
271      i_name = input.name.to_s
272      out.write "#{i_name}_from_history = ARGV[#{i*2}].chomp\n"
273      out.write "#{i_name}_tmp = ARGV[#{i*2+1}].chomp\n"
274      out.write "if #{i_name}_from_history == \"true\"\n"
275      out.write "  chomp_last_newline(#{i_name}_tmp)\n"
276      out.write "  run.upload_input_file('#{i_name}', #{i_name}_tmp)\n"
277      out.write "else\n"
278      out.write "  run.set_input('#{i_name}', sanitize(#{i_name}_tmp))\n"
279      out.write "end\n"
280    end
281
282  end
283
284
285  def script_start_run(out)
286    out.write <<START_RUN
287
288# start run and wait until it is finished
289run.start
290run.wait(:progress => true)
291
292START_RUN
293  end
294
295
296  def script_get_outputs(out, me_rest)
297    outputs_start_index = me_rest.workflow.inputs.size * 2
298    outputs_end_index = outputs_start_index + me_rest.workflow.outputs.size - 1
299    out.write "# get output arguments and associated them with a file\n"
300    outputs_start_index.upto(outputs_end_index) do |o|
301      out.write "output#{o} = File.open(ARGV[#{o}], \"w\")\n"
302      out.write "begin\n"
303      out.write "  get_outputs(run, false, output#{o}, '" + me_rest.workflow.outputs[o - outputs_start_index].name.to_s + "')\n"
304      out.write "rescue Exception => err\n"
305      out.write "  get_outputs(run, false, output#{o}, '" + me_rest.workflow.outputs[o - outputs_start_index].name.to_s + ".error')\n"
306      out.write "end\n"
307    end
308
309  end
310
311
312  def script_finish_run(out)
313    out.write "\n# delete run\n"
314    out.write "run.delete\n"
315  end
316
317
318
319  # public methods from here onwards
320  public
321
322  # Generates the Galaxy tool's xml file responsible for the UI.
323  def generate_xml(me_rest, xml_file)
324    out = File.open(xml_file, "w")
325    tool_b(out, me_rest.workflow.title)
326    command_be(out, me_rest, xml_file.gsub('.xml', '.rb'))
327    inputs_be(out, me_rest.workflow.inputs)
328    outputs_be(out, me_rest.workflow.outputs)
329    help_be(out, me_rest)
330    tool_e(out)
331    out.close
332  end
333
334  #
335  # Generates the Galaxy tool's script file responsible for talking to the
336  # taverna server
337  #
338  def generate_script(me_rest, t2_server, script_file)
339    out = File.open(script_file, "w")
340    script_preample(out)
341    script_util_methods(out)
342    script_create_t2_run(out, me_rest.uri, t2_server)
343    script_init_inputs(out, me_rest)
344    script_start_run(out)
345    script_get_outputs(out, me_rest)
346    script_finish_run(out)
347    out.close
348  end
349
350end
Note: See TracBrowser for help on using the browser.