sequenceserver-beta 0.8.7.beta1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +3 -0
  3. data/LICENSE.Apache.txt +176 -0
  4. data/LICENSE.txt +69 -0
  5. data/README.txt +5 -0
  6. data/bin/sequenceserver +82 -0
  7. data/config.ru +6 -0
  8. data/example.config.yml +39 -0
  9. data/lib/profile_code.rb +217 -0
  10. data/lib/sequenceserver.rb +527 -0
  11. data/lib/sequenceserver/blast.rb +92 -0
  12. data/lib/sequenceserver/customisation.rb +60 -0
  13. data/lib/sequenceserver/database.rb +29 -0
  14. data/lib/sequenceserver/database_formatter.rb +190 -0
  15. data/lib/sequenceserver/helpers.rb +136 -0
  16. data/lib/sequenceserver/sequencehelpers.rb +93 -0
  17. data/lib/sequenceserver/sinatralikeloggerformatter.rb +12 -0
  18. data/lib/sequenceserver/version.rb +9 -0
  19. data/public/css/beige.css.css +254 -0
  20. data/public/css/bootstrap.dropdown.css +29 -0
  21. data/public/css/bootstrap.icons.css +155 -0
  22. data/public/css/bootstrap.min.css +415 -0
  23. data/public/css/bootstrap.modal.css +28 -0
  24. data/public/css/custom.css +232 -0
  25. data/public/img/glyphicons-halflings-white.png +0 -0
  26. data/public/img/glyphicons-halflings.png +0 -0
  27. data/public/js/bootstrap.dropdown.js +92 -0
  28. data/public/js/bootstrap.modal.js +7 -0
  29. data/public/js/bootstrap.transition.js +7 -0
  30. data/public/js/jquery-scrollspy.js +98 -0
  31. data/public/js/jquery-ui.js +14987 -0
  32. data/public/js/jquery.activity.js +10 -0
  33. data/public/js/jquery.enablePlaceholder.min.js +10 -0
  34. data/public/js/jquery.js +5 -0
  35. data/public/js/sequenceserver.blast.js +208 -0
  36. data/public/js/sequenceserver.js +304 -0
  37. data/public/js/store.min.js +2 -0
  38. data/sequenceserver.gemspec +49 -0
  39. data/tests/database/nucleotide/Sinvicta2-2-3.cdna.subset.fasta +5486 -0
  40. data/tests/database/nucleotide/Sinvicta2-2-3.cdna.subset.fasta.nhr +0 -0
  41. data/tests/database/nucleotide/Sinvicta2-2-3.cdna.subset.fasta.nin +0 -0
  42. data/tests/database/nucleotide/Sinvicta2-2-3.cdna.subset.fasta.nsq +0 -0
  43. data/tests/database/protein/Sinvicta2-2-3.prot.subset.fasta +6449 -0
  44. data/tests/database/protein/Sinvicta2-2-3.prot.subset.fasta.phr +0 -0
  45. data/tests/database/protein/Sinvicta2-2-3.prot.subset.fasta.pin +0 -0
  46. data/tests/database/protein/Sinvicta2-2-3.prot.subset.fasta.psq +0 -0
  47. data/tests/run +26 -0
  48. data/tests/test_sequencehelpers.rb +77 -0
  49. data/tests/test_sequenceserver_blast.rb +60 -0
  50. data/tests/test_ui.rb +104 -0
  51. data/tests/test_ui.rb~ +104 -0
  52. data/tests/ui.specs.todo +10 -0
  53. data/views/500.erb +22 -0
  54. data/views/_options.erb +144 -0
  55. data/views/search.erb +220 -0
  56. metadata +226 -0
@@ -0,0 +1,527 @@
1
+ # sequenceserver.rb
2
+
3
+ require 'sinatra/base'
4
+ require 'yaml'
5
+ require 'logger'
6
+ require 'fileutils'
7
+ require 'sequenceserver/helpers'
8
+ require 'sequenceserver/blast'
9
+ require 'sequenceserver/sequencehelpers'
10
+ require 'sequenceserver/sinatralikeloggerformatter'
11
+ require 'sequenceserver/customisation'
12
+ require 'sequenceserver/version'
13
+
14
+ # Helper module - initialize the blast server.
15
+ module SequenceServer
16
+ class App < Sinatra::Base
17
+ include Helpers
18
+ include SequenceHelpers
19
+ include SequenceServer::Customisation
20
+
21
+ # Basic configuration settings for app.
22
+ configure do
23
+ # enable some builtin goodies
24
+ enable :session, :logging
25
+
26
+ # main application file
27
+ set :app_file, File.expand_path(__FILE__)
28
+
29
+ # app root is SequenceServer's installation directory
30
+ #
31
+ # SequenceServer figures out different settings, location of static
32
+ # assets or templates for example, based on app root.
33
+ set :root, File.dirname(File.dirname(app_file))
34
+
35
+ # path to test database
36
+ #
37
+ # SequenceServer ships with test database (fire ant genome) so users can
38
+ # launch and preview SequenceServer without any configuration, and/or run
39
+ # test suite.
40
+ set :test_database, File.join(root, 'tests', 'database')
41
+
42
+ # path to example configuration file
43
+ #
44
+ # SequenceServer ships with a dummy configuration file. Users can simply
45
+ # copy it and make necessary changes to get started.
46
+ set :example_config_file, File.join(root, 'example.config.yml')
47
+
48
+ # path to SequenceServer's configuration file
49
+ #
50
+ # The configuration file is a simple, YAML data store.
51
+ set :config_file, Proc.new{ File.expand_path('~/.sequenceserver.conf') }
52
+
53
+ set :log, Proc.new { Logger.new(STDERR) }
54
+ log.formatter = SinatraLikeLogFormatter.new()
55
+ end
56
+
57
+ # Local, app configuration settings derived from config.yml.
58
+ #
59
+ # A config.yml should contain the settings described in the following
60
+ # configure block as key, value pairs. See example.config.yml in the
61
+ # installation directory.
62
+ configure do
63
+ # store the settings hash from config.yml; further configuration values
64
+ # are derived from it
65
+ set :config, {}
66
+
67
+ # absolute path to the blast binaries
68
+ #
69
+ # A default of 'nil' is indicative of blast binaries being present in
70
+ # system PATH.
71
+ set :bin, Proc.new{ File.expand_path(config['bin']) rescue nil }
72
+
73
+ # absolute path to the database directory
74
+ #
75
+ # As a default use 'database' directory relative to current working
76
+ # directory of the running app.
77
+ set :database, Proc.new{ File.expand_path(config['database']) rescue test_database }
78
+
79
+ # the port number to run Sequence Server standalone
80
+ set :port, Proc.new{ (config['port'] or 4567).to_i }
81
+
82
+ # number of threads to be used during blasting
83
+ #
84
+ # This option is passed directly to BLAST+. We use a default value of 1
85
+ # as a higher value may cause BLAST+ to crash if it was not compiled with
86
+ # threading support.
87
+ set :num_threads, Proc.new{ (config['num_threads'] or 1).to_i }
88
+ end
89
+
90
+ # Lookup tables used by Sequence Server to pick up the right blast binary,
91
+ # or database. These tables should be populated during app initialization
92
+ # by scanning bin, and database directories.
93
+ configure do
94
+ # blast methods (executables) and their corresponding absolute path
95
+ set :binaries, {}
96
+
97
+ # list of blast databases indexed by their hash value
98
+ set :databases, {}
99
+ end
100
+
101
+ configure :development do
102
+ log.level = Logger::DEBUG
103
+ end
104
+
105
+ configure(:production) do
106
+ log.level = Logger::INFO
107
+ error do
108
+ erb :'500'
109
+ end
110
+ not_found do
111
+ erb :'500'
112
+ end
113
+ end
114
+
115
+ class << self
116
+ # Run SequenceServer as a self-hosted server.
117
+ #
118
+ # By default SequenceServer uses Thin, Mongrel or WEBrick (in that
119
+ # order). This can be configured by setting the 'server' option.
120
+ def run!(options={})
121
+ set options
122
+
123
+ # perform SequenceServer initializations
124
+ puts "\n== Initializing SequenceServer..."
125
+ init
126
+
127
+ # find out the what server to host SequenceServer with
128
+ handler = detect_rack_handler
129
+ handler_name = handler.name.gsub(/.*::/, '')
130
+
131
+ puts
132
+ log.info("Using #{handler_name} web server.")
133
+
134
+ if handler_name == 'WEBrick'
135
+ puts "\n== We recommend using Thin web server for better performance."
136
+ puts "== To install Thin: [sudo] gem install thin"
137
+ end
138
+
139
+ url = "http://#{bind}:#{port}"
140
+ puts "\n== Launched SequenceServer at: #{url}"
141
+ puts "== Press CTRL + C to quit."
142
+ handler.run(self, :Host => bind, :Port => port, :Logger => Logger.new('/dev/null')) do |server|
143
+ [:INT, :TERM].each { |sig| trap(sig) { quit!(server, handler) } }
144
+ set :running, true
145
+
146
+ # for Thin
147
+ server.silent = true if handler_name == 'Thin'
148
+ end
149
+ rescue Errno::EADDRINUSE, RuntimeError => e
150
+ puts "\n== Failed to start SequenceServer."
151
+ puts "== Is SequenceServer already running at: #{url}"
152
+ end
153
+
154
+ # Stop SequenceServer.
155
+ def quit!(server, handler_name)
156
+ # Use Thin's hard #stop! if available, otherwise just #stop.
157
+ server.respond_to?(:stop!) ? server.stop! : server.stop
158
+ puts "\n== Thank you for using SequenceServer :)." +
159
+ "\n== Please cite: " +
160
+ "\n== Priyam A., Woodcroft B.J., Wurm Y (in prep)." +
161
+ "\n== Sequenceserver: BLAST searching made easy." unless handler_name =~/cgi/i
162
+ end
163
+
164
+ # Initializes the blast server : executables, database. Exit if blast
165
+ # executables, and databses can not be found. Logs the result if logging
166
+ # has been enabled.
167
+ def init
168
+ # first read the user supplied configuration options
169
+ self.config = parse_config
170
+
171
+ # empty config file
172
+ unless config
173
+ log.warn("Empty configuration file: #{config_file} - will assume default settings")
174
+ self.config = {}
175
+ end
176
+
177
+ # scan for blast binaries
178
+ self.binaries = scan_blast_executables(bin).freeze
179
+
180
+ # Log the discovery of binaries.
181
+ binaries.each do |command, path|
182
+ log.info("Found #{command} at #{path}")
183
+ end
184
+
185
+ # scan for blast database
186
+ self.databases = scan_blast_db(database, binaries['blastdbcmd']).freeze
187
+
188
+ # Log the discovery of databases.
189
+ databases.each do |id, database|
190
+ log.info("Found #{database.type} database: #{database.title} at #{database.name}")
191
+ end
192
+ rescue IOError => error
193
+ log.fatal("Fail: #{error}")
194
+ exit
195
+ rescue ArgumentError => error
196
+ log.fatal("Error in config.yml: #{error}")
197
+ puts "YAML is white space sensitive. Is your config.yml properly indented?"
198
+ exit
199
+ rescue Errno::ENOENT # config file not found
200
+ log.info('Configuration file not found')
201
+ FileUtils.cp(example_config_file, config_file)
202
+ log.info("Generated a dummy configuration file: #{config_file}")
203
+ puts "\nPlease edit #{config_file} to indicate the location of your BLAST databases and run SequenceServer again."
204
+ exit
205
+ end
206
+
207
+ # Parse config.yml, and return the resulting hash.
208
+ #
209
+ # This method uses YAML.load_file to read config.yml. Absence of a
210
+ # config.yml is safely ignored as the app should then fall back on
211
+ # default configuration values. Any other error raised by YAML.load_file
212
+ # is not rescued.
213
+ def parse_config
214
+ YAML.load_file( config_file )
215
+ end
216
+ end
217
+
218
+ get '/' do
219
+ erb :search
220
+ end
221
+
222
+ before '/' do
223
+ pass if params.empty?
224
+
225
+ # ensure required params present
226
+ #
227
+ # If a required parameter is missing, SequnceServer returns 'Bad Request
228
+ # (400)' error.
229
+ #
230
+ # See Twitter's [Error Codes & Responses][1] page for reference.
231
+ #
232
+ # [1]: https://dev.twitter.com/docs/error-codes-responses
233
+
234
+ if params[:method].nil? or params[:method].empty?
235
+ halt 400, "No BLAST method provided."
236
+ end
237
+
238
+ if params[:sequence].nil? or params[:sequence].empty?
239
+ halt 400, "No input sequence provided."
240
+ end
241
+
242
+ if params[:databases].nil?
243
+ halt 400, "No BLAST database provided."
244
+ end
245
+
246
+ # ensure params are valid #
247
+
248
+ # only allowed blast methods should be used
249
+ blast_methods = %w|blastn blastp blastx tblastn tblastx|
250
+ unless blast_methods.include?(params[:method])
251
+ halt 400, "Unknown BLAST method: #{params[:method]}."
252
+ end
253
+
254
+ # check the advanced options are sensible
255
+ begin #FIXME
256
+ validate_advanced_parameters(params[:advanced])
257
+ rescue ArgumentError => error
258
+ halt 400, "Advanced parameters invalid: #{error}"
259
+ end
260
+
261
+ # log params
262
+ settings.log.debug('method: ' + params[:method])
263
+ settings.log.debug('sequence: ' + params[:sequence])
264
+ settings.log.debug('database: ' + params[:databases].inspect)
265
+ settings.log.debug('advanced: ' + params[:advanced])
266
+ end
267
+
268
+ post '/' do
269
+ method = params['method']
270
+ databases = params[:databases]
271
+ sequence = params[:sequence]
272
+ advanced_opts = params['advanced']
273
+
274
+ # evaluate empty sequence as nil, otherwise as fasta
275
+ sequence = sequence.empty? ? nil : to_fasta(sequence)
276
+
277
+ # blastn implies blastn, not megablast; but let's not interfere if a user
278
+ # specifies `task` herself
279
+ if method == 'blastn' and not advanced_opts =~ /task/
280
+ advanced_opts << ' -task blastn '
281
+ end
282
+
283
+ method = settings.binaries[ method ]
284
+ databases = params[:databases].map{|index|
285
+ settings.databases[index].name
286
+ }
287
+ advanced_opts << " -num_threads #{settings.num_threads}"
288
+
289
+ # run blast and log
290
+ blast = Blast.new(method, sequence, databases.join(' '), advanced_opts)
291
+ blast.run!
292
+ settings.log.info('Ran: ' + blast.command)
293
+
294
+ unless blast.success?
295
+ halt *blast.error
296
+ end
297
+
298
+ format_blast_results(blast.result, databases)
299
+ end
300
+
301
+ # get '/get_sequence/?id=sequence_ids&db=retreival_databases'
302
+ #
303
+ # Use whitespace to separate entries in sequence_ids (all other chars exist
304
+ # in identifiers) and retreival_databases (we don't allow whitespace in a
305
+ # database's name, so it's safe).
306
+ get '/get_sequence/' do
307
+ sequenceids = params[:id].split(/\s/).uniq # in a multi-blast
308
+ # query some may have been found multiply
309
+ retrieval_databases = params[:db].split(/\s/)
310
+
311
+ settings.log.info("Looking for: '#{sequenceids.join(', ')}' in '#{retrieval_databases.join(', ')}'")
312
+
313
+ # the results do not indicate which database a hit is from.
314
+ # Thus if several databases were used for blasting, we must check them all
315
+ # if it works, refactor with "inject" or "collect"?
316
+ found_sequences = ''
317
+
318
+ retrieval_databases.each do |database| # we need to populate this session variable from the erb.
319
+ sequence = sequence_from_blastdb(sequenceids, database)
320
+ if sequence.empty?
321
+ settings.log.debug("'#{sequenceids.join(', ')}' not found in #{database}")
322
+ else
323
+ found_sequences += sequence
324
+ end
325
+ end
326
+
327
+ found_sequences_count = found_sequences.count('>')
328
+
329
+ out = ''
330
+ # just in case, checking we found right number of sequences
331
+ if found_sequences_count != sequenceids.length
332
+ out <<<<HEADER
333
+ <h1>ERROR: incorrect number of sequences found.</h1>
334
+ <p>Dear user,</p>
335
+
336
+ <p><strong>We have found
337
+ <em>#{found_sequences_count > sequenceids.length ? 'more' : 'less'}</em>
338
+ sequence than expected.</strong></p>
339
+
340
+ <p>This is likely due to a problem with how databases are formatted.
341
+ <strong>Please share this text with the person managing this website so
342
+ they can resolve the issue.</strong></p>
343
+
344
+ <p> You requested #{sequenceids.length} sequence#{sequenceids.length > 1 ? 's' : ''}
345
+ with the following identifiers: <code>#{sequenceids.join(', ')}</code>,
346
+ from the following databases: <code>#{retrieval_databases.join(', ')}</code>.
347
+ But we found #{found_sequences_count} sequence#{found_sequences_count> 1 ? 's' : ''}.
348
+ </p>
349
+
350
+ <p>If sequences were retrieved, you can find them below (but some may be incorrect, so be careful!).</p>
351
+ <hr/>
352
+ HEADER
353
+ end
354
+
355
+ out << "<pre><code>#{found_sequences}</pre></code>"
356
+ out
357
+ end
358
+
359
+ # Ensure a '>sequence_identifier\n' at the start of a sequence.
360
+ #
361
+ # An empty query line appears in the blast report if the leading
362
+ # '>sequence_identifier\n' in the sequence is missing. We prepend
363
+ # the input sequence with user info in such case.
364
+ #
365
+ # > to_fasta("acgt")
366
+ # => 'Submitted_By_127.0.0.1_at_110214-15:33:34\nacgt'
367
+ def to_fasta(sequence)
368
+ sequence.lstrip!
369
+ if sequence[0,1] != '>'
370
+ ip = request.ip.to_s
371
+ time = Time.now.strftime("%y%m%d-%H:%M:%S")
372
+ sequence.insert(0, ">Submitted_By_#{ip}_at_#{time}\n")
373
+ end
374
+ return sequence
375
+ end
376
+
377
+ def format_blast_results(result, databases)
378
+ # Constructing the result in an Array and then calling Array#join is much faster than
379
+ # building up a String and using +=, as older versions of SeqServ did.
380
+ formatted_results = []
381
+
382
+ @all_retrievable_ids = []
383
+ string_of_used_databases = databases.join(' ')
384
+ blast_database_number = 0
385
+ line_number = 0
386
+ started_query = false
387
+ finished_database_summary = false
388
+ finished_alignments = false
389
+ reference_string = ''
390
+ database_summary_string = ''
391
+ result.each do |line|
392
+ line_number += 1
393
+ next if line_number <= 5 #skip the first 5 lines
394
+
395
+ # Add the reference to the end, not the start, of the blast result
396
+ if line_number >= 7 and line_number <= 15
397
+ reference_string += line
398
+ next
399
+ end
400
+
401
+ if !finished_database_summary and line_number > 15
402
+ database_summary_string += line
403
+ finished_database_summary = true if line.match(/total letters/)
404
+ next
405
+ end
406
+
407
+ # Remove certain lines from the output
408
+ skipped_lines = [/^<\/BODY>/,/^<\/HTML>/,/^<\/PRE>/]
409
+ skip = false
410
+ skipped_lines.each do |skippy|
411
+ # $stderr.puts "`#{line}' matches #{skippy}?"
412
+ if skippy.match(line)
413
+ skip = true
414
+ # $stderr.puts 'yes'
415
+ else
416
+ # $stderr.puts 'no'
417
+ end
418
+ end
419
+ next if skip
420
+
421
+ # Remove the javascript inclusion
422
+ line.gsub!(/^<script src=\"blastResult.js\"><\/script>/, '')
423
+
424
+ if line.match(/^>/) # If line to possibly replace
425
+ # Reposition the anchor to the end of the line, so that it both still works and
426
+ # doesn't interfere with the diagnostic space at the beginning of the line.
427
+ #
428
+ # There are two cases:
429
+ #
430
+ # database formatted _with_ -parse_seqids
431
+ line.gsub!(/^>(.+)(<a.*><\/a>)(.*)/, '>\1\3\2')
432
+ #
433
+ # database formatted _without_ -parse_seqids
434
+ line.gsub!(/^>(<a.*><\/a>)(.*)/, '>\2\1')
435
+
436
+ # get hit coordinates -- useful for linking to genome browsers
437
+ hit_length = result[line_number..-1].index{|l| l =~ />lcl|Lambda/}
438
+ hit_coordinates = result[line_number, hit_length].grep(/Sbjct/).
439
+ map(&:split).map{|l| [l[1], l[-1]]}.flatten.map(&:to_i).minmax
440
+
441
+ # Create the hyperlink (if required)
442
+ formatted_results << construct_sequence_hyperlink_line(line, databases, hit_coordinates)
443
+ else
444
+ # Surround each query's result in <div> tags so they can be coloured by CSS
445
+ if matches = line.match(/^<b>Query=<\/b> (.*)/) # If starting a new query, then surround in new <div> tag, and finish the last one off
446
+ line = "<div class=\"resultn\" id=\"#{matches[1]}\">\n<h3>Query= #{matches[1]}</h3><pre>"
447
+ unless blast_database_number == 0
448
+ line = "</pre></div>\n#{line}"
449
+ end
450
+ blast_database_number += 1
451
+ elsif line.match(/^ Database: /) and !finished_alignments
452
+ formatted_results << "</div>\n<pre>#{database_summary_string}\n\n"
453
+ finished_alignments = true
454
+ end
455
+ formatted_results << line
456
+ end
457
+ end
458
+ formatted_results << "</pre>"
459
+
460
+ link_to_fasta_of_all = "/get_sequence/?id=#{@all_retrievable_ids.join(' ')}&db=#{string_of_used_databases}"
461
+ # #dbs must be sep by ' '
462
+ retrieval_text = @all_retrievable_ids.empty? ? '' : "<a href='#{url(link_to_fasta_of_all)}'>FASTA of #{@all_retrievable_ids.length} retrievable hit(s)</a>"
463
+
464
+ "<h2>Results</h2>"+
465
+ retrieval_text +
466
+ "<br/><br/>" +
467
+ formatted_results.join +
468
+ "<br/>" +
469
+ "<pre>#{reference_string.strip}</pre>"
470
+ end
471
+
472
+ def construct_sequence_hyperlink_line(line, databases, hit_coordinates)
473
+ matches = line.match(/^>(.+)/)
474
+ sequence_id = matches[1]
475
+
476
+ link = nil
477
+
478
+ # If a custom sequence hyperlink method has been defined,
479
+ # use that.
480
+ options = {
481
+ :sequence_id => sequence_id,
482
+ :databases => databases,
483
+ :hit_coordinates => hit_coordinates
484
+ }
485
+
486
+ # First precedence: construct the whole line to be customised
487
+ if self.respond_to?(:construct_custom_sequence_hyperlinking_line)
488
+ settings.log.debug("Using custom hyperlinking line creator with sequence #{options.inspect}")
489
+ link_line = construct_custom_sequence_hyperlinking_line(options)
490
+ unless link_line.nil?
491
+ return link_line
492
+ end
493
+ end
494
+
495
+ # If we have reached here, custom construction of the
496
+ # whole line either wasn't defined, or returned nil
497
+ # (indicating failure)
498
+ if self.respond_to?(:construct_custom_sequence_hyperlink)
499
+ settings.log.debug("Using custom hyperlink creator with sequence #{options.inspect}")
500
+ link = construct_custom_sequence_hyperlink(options)
501
+ else
502
+ settings.log.debug("Using standard hyperlink creator with sequence `#{options.inspect}'")
503
+ link = construct_standard_sequence_hyperlink(options)
504
+ end
505
+
506
+ # Return the BLAST output line with the link in it
507
+ if link.nil?
508
+ settings.log.debug('No link added link for: `'+ sequence_id +'\'')
509
+ return line
510
+ else
511
+ settings.log.debug('Added link for: `'+ sequence_id +'\''+ link)
512
+ return "><a href='#{url(link)}' target='_blank'>#{sequence_id}</a> \n"
513
+ end
514
+
515
+ end
516
+
517
+ # Advanced options are specified by the user. Here they are checked for interference with SequenceServer operations.
518
+ # raise ArgumentError if an error has occurred, otherwise return without value
519
+ def validate_advanced_parameters(advanced_options)
520
+ raise ArgumentError, "Invalid characters detected in the advanced options" unless advanced_options =~ /\A[a-z0-9\-_\. ']*\Z/i
521
+ disallowed_options = %w(-out -html -outfmt -db -query)
522
+ disallowed_options.each do |o|
523
+ raise ArgumentError, "The advanced BLAST option \"#{o}\" is used internally by SequenceServer and so cannot be specified by the you" if advanced_options =~ /#{o}/i
524
+ end
525
+ end
526
+ end
527
+ end