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