sequenceserver-beta 0.8.7.beta1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile +3 -0
- data/LICENSE.Apache.txt +176 -0
- data/LICENSE.txt +69 -0
- data/README.txt +5 -0
- data/bin/sequenceserver +82 -0
- data/config.ru +6 -0
- data/example.config.yml +39 -0
- data/lib/profile_code.rb +217 -0
- data/lib/sequenceserver.rb +527 -0
- data/lib/sequenceserver/blast.rb +92 -0
- data/lib/sequenceserver/customisation.rb +60 -0
- data/lib/sequenceserver/database.rb +29 -0
- data/lib/sequenceserver/database_formatter.rb +190 -0
- data/lib/sequenceserver/helpers.rb +136 -0
- data/lib/sequenceserver/sequencehelpers.rb +93 -0
- data/lib/sequenceserver/sinatralikeloggerformatter.rb +12 -0
- data/lib/sequenceserver/version.rb +9 -0
- data/public/css/beige.css.css +254 -0
- data/public/css/bootstrap.dropdown.css +29 -0
- data/public/css/bootstrap.icons.css +155 -0
- data/public/css/bootstrap.min.css +415 -0
- data/public/css/bootstrap.modal.css +28 -0
- data/public/css/custom.css +232 -0
- data/public/img/glyphicons-halflings-white.png +0 -0
- data/public/img/glyphicons-halflings.png +0 -0
- data/public/js/bootstrap.dropdown.js +92 -0
- data/public/js/bootstrap.modal.js +7 -0
- data/public/js/bootstrap.transition.js +7 -0
- data/public/js/jquery-scrollspy.js +98 -0
- data/public/js/jquery-ui.js +14987 -0
- data/public/js/jquery.activity.js +10 -0
- data/public/js/jquery.enablePlaceholder.min.js +10 -0
- data/public/js/jquery.js +5 -0
- data/public/js/sequenceserver.blast.js +208 -0
- data/public/js/sequenceserver.js +304 -0
- data/public/js/store.min.js +2 -0
- data/sequenceserver.gemspec +49 -0
- data/tests/database/nucleotide/Sinvicta2-2-3.cdna.subset.fasta +5486 -0
- data/tests/database/nucleotide/Sinvicta2-2-3.cdna.subset.fasta.nhr +0 -0
- data/tests/database/nucleotide/Sinvicta2-2-3.cdna.subset.fasta.nin +0 -0
- data/tests/database/nucleotide/Sinvicta2-2-3.cdna.subset.fasta.nsq +0 -0
- data/tests/database/protein/Sinvicta2-2-3.prot.subset.fasta +6449 -0
- data/tests/database/protein/Sinvicta2-2-3.prot.subset.fasta.phr +0 -0
- data/tests/database/protein/Sinvicta2-2-3.prot.subset.fasta.pin +0 -0
- data/tests/database/protein/Sinvicta2-2-3.prot.subset.fasta.psq +0 -0
- data/tests/run +26 -0
- data/tests/test_sequencehelpers.rb +77 -0
- data/tests/test_sequenceserver_blast.rb +60 -0
- data/tests/test_ui.rb +104 -0
- data/tests/test_ui.rb~ +104 -0
- data/tests/ui.specs.todo +10 -0
- data/views/500.erb +22 -0
- data/views/_options.erb +144 -0
- data/views/search.erb +220 -0
- 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
|