sequenceserver 1.0.0.pre.3 → 1.0.0.pre.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +47 -4
- data/lib/sequenceserver.rb +3 -177
- data/lib/sequenceserver/blast.rb +4 -59
- data/lib/sequenceserver/blast/constants.rb +34 -0
- data/lib/sequenceserver/blast/formatter.rb +69 -0
- data/lib/sequenceserver/blast/hit.rb +33 -12
- data/lib/sequenceserver/blast/hsp.rb +4 -6
- data/lib/sequenceserver/blast/query.rb +6 -8
- data/lib/sequenceserver/blast/report.rb +94 -77
- data/lib/sequenceserver/config.rb +1 -0
- data/lib/sequenceserver/exceptions.rb +1 -1
- data/lib/sequenceserver/links.rb +14 -21
- data/lib/sequenceserver/routes.rb +163 -0
- data/lib/sequenceserver/search.rb +19 -0
- data/lib/sequenceserver/sequence.rb +135 -30
- data/public/css/custom.css +17 -8
- data/public/dist/css/sequenceserver.min.css +1 -1
- data/public/dist/css/sequenceserver.min.css.gz +0 -0
- data/public/dist/js/sequenceserver.min.js +1 -1
- data/public/dist/js/sequenceserver.min.js.gz +0 -0
- data/public/js/sequenceserver.js +66 -32
- data/sequenceserver.gemspec +1 -1
- data/views/result.erb +8 -8
- data/views/search.erb +12 -3
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9ed1609668685537118f3ff44bb18bda69cbdf8b
|
4
|
+
data.tar.gz: d2b5aa23928abb0ed230747e789b45440b9927fa
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a2b7d392d183377a918b4841685adc34fe26997b73eda7f5129273064bf8bae30929448ea8fd4818508728957460cc20c299b375ba644405060ccbed432ac5fa
|
7
|
+
data.tar.gz: bdca19b354e1b70113d2c77fc99699c4150e30e61e8fe5a82195d8e8033ad826a82ea7fdd9fde69a0528197d4aa739648f2bfd0d31f81d2da25dd7b32e520bfc
|
data/README.md
CHANGED
@@ -1,7 +1,10 @@
|
|
1
1
|
[![build status](https://secure.travis-ci.org/yannickwurm/sequenceserver.png?branch=master)](https://travis-ci.org/yannickwurm/sequenceserver)
|
2
|
-
[![
|
3
|
-
[![
|
4
|
-
[![
|
2
|
+
[![code climate](https://codeclimate.com/github/yannickwurm/sequenceserver/badges/gpa.svg)](https://codeclimate.com/github/yannickwurm/sequenceserver)
|
3
|
+
[![coverage](https://codeclimate.com/github/yannickwurm/sequenceserver/badges/coverage.svg)](https://codeclimate.com/github/yannickwurm/sequenceserver)
|
4
|
+
[![gem version](https://badge.fury.io/rb/sequenceserver.svg)](http://rubygems.org/gems/sequenceserver)
|
5
|
+
[![total downloads](http://ruby-gem-downloads-badge.herokuapp.com/sequenceserver?type=total&color=brightgreen)](http://rubygems.org/gems/sequenceserver)
|
6
|
+
|
7
|
+
[![gitter chat](https://badges.gitter.im/gitterHQ/gitter.png)](https://gitter.im/yannickwurm/sequenceserver)
|
5
8
|
|
6
9
|
# SequenceServer - BLAST searching made easy!
|
7
10
|
|
@@ -10,7 +13,47 @@ interface for use locally or over the web.
|
|
10
13
|
|
11
14
|
## Installation
|
12
15
|
|
13
|
-
Please see http://www.sequenceserver.com
|
16
|
+
Please see http://www.sequenceserver.com.
|
17
|
+
|
18
|
+
## Contribute
|
19
|
+
|
20
|
+
You will need Ruby and NodeJS and respective package managers (RubyGems and
|
21
|
+
npm) for development.
|
22
|
+
|
23
|
+
##### Get source code.
|
24
|
+
```
|
25
|
+
git clone https://github.com/yannickwurm/sequenceserver
|
26
|
+
cd sequenceserver
|
27
|
+
```
|
28
|
+
|
29
|
+
##### Install dependencies.
|
30
|
+
###### Ruby
|
31
|
+
```
|
32
|
+
gem install bundler && bundle
|
33
|
+
```
|
34
|
+
|
35
|
+
###### Node
|
36
|
+
```
|
37
|
+
npm install
|
38
|
+
```
|
39
|
+
|
40
|
+
#### Run, test, lint
|
41
|
+
```
|
42
|
+
# Launch SequenceServer in development mode.
|
43
|
+
bundle exec bin/sequenceserver -D
|
44
|
+
|
45
|
+
# Run RSpec, Capybara, and RuboCop.
|
46
|
+
rake
|
47
|
+
|
48
|
+
# Run bootlint, csslint, jshint
|
49
|
+
npm run-script cop
|
50
|
+
|
51
|
+
# Minify JS and CSS
|
52
|
+
npm run-script build
|
53
|
+
```
|
54
|
+
|
55
|
+
SequenceServer runs in production mode by default. Minified JS and CSS are
|
56
|
+
picked in production mode only.
|
14
57
|
|
15
58
|
## Contributors
|
16
59
|
|
data/lib/sequenceserver.rb
CHANGED
@@ -1,16 +1,14 @@
|
|
1
|
-
require 'yaml'
|
2
1
|
require 'English'
|
3
|
-
require 'fileutils'
|
4
|
-
require 'sinatra/base'
|
5
2
|
require 'thin'
|
6
|
-
require 'json'
|
7
3
|
|
8
4
|
require 'sequenceserver/exceptions'
|
9
5
|
require 'sequenceserver/config'
|
10
6
|
require 'sequenceserver/logger'
|
7
|
+
require 'sequenceserver/search'
|
11
8
|
require 'sequenceserver/sequence'
|
12
9
|
require 'sequenceserver/database'
|
13
10
|
require 'sequenceserver/blast'
|
11
|
+
require 'sequenceserver/routes'
|
14
12
|
|
15
13
|
# Top level module / namespace.
|
16
14
|
module SequenceServer
|
@@ -90,7 +88,7 @@ module SequenceServer
|
|
90
88
|
# controller.
|
91
89
|
def call(env)
|
92
90
|
env['rack.logger'] = logger
|
93
|
-
|
91
|
+
Routes.call(env)
|
94
92
|
end
|
95
93
|
|
96
94
|
# Run SequenceServer interactively.
|
@@ -201,176 +199,4 @@ module SequenceServer
|
|
201
199
|
system("which #{command} > /dev/null 2>&1")
|
202
200
|
end
|
203
201
|
end
|
204
|
-
|
205
|
-
# Controller.
|
206
|
-
class App < Sinatra::Base
|
207
|
-
# See
|
208
|
-
# http://www.sinatrarb.com/configuration.html
|
209
|
-
configure do
|
210
|
-
# We don't need Rack::MethodOverride. Let's avoid the overhead.
|
211
|
-
disable :method_override
|
212
|
-
|
213
|
-
# Ensure exceptions never leak out of the app. Exceptions raised within
|
214
|
-
# the app must be handled by the app. We do this by attaching error
|
215
|
-
# blocks to exceptions we know how to handle and attaching to Exception
|
216
|
-
# as fallback.
|
217
|
-
disable :show_exceptions, :raise_errors
|
218
|
-
|
219
|
-
# Make it a policy to dump to 'rack.errors' any exception raised by the
|
220
|
-
# app so that error handlers don't have to do it themselves. But for it
|
221
|
-
# to always work, Exceptions defined by us should not respond to `code`
|
222
|
-
# or http_status` methods. Error blocks errors must explicitly set http
|
223
|
-
# status, if needed, by calling `status` method.
|
224
|
-
# method.
|
225
|
-
enable :dump_errors
|
226
|
-
|
227
|
-
# We don't want Sinatra do setup any loggers for us. We will use our own.
|
228
|
-
set :logging, nil
|
229
|
-
|
230
|
-
# Public, and views directory will be found here.
|
231
|
-
set :root, lambda { SequenceServer.root }
|
232
|
-
end
|
233
|
-
|
234
|
-
# See
|
235
|
-
# http://www.sinatrarb.com/intro.html#Mime%20Types
|
236
|
-
configure do
|
237
|
-
mime_type :fasta, 'text/fasta'
|
238
|
-
mime_type :xml, 'text/xml'
|
239
|
-
mime_type :tsv, 'text/tsv'
|
240
|
-
end
|
241
|
-
|
242
|
-
configure :production do
|
243
|
-
set :public_folder,
|
244
|
-
lambda { File.join SequenceServer.root, 'public', 'dist' }
|
245
|
-
end
|
246
|
-
|
247
|
-
helpers do
|
248
|
-
# Render an anchor element from the given Hash.
|
249
|
-
#
|
250
|
-
# See links.rb for example of a Hash object that will be rendered.
|
251
|
-
def a(link)
|
252
|
-
return unless link[:title] && link[:url]
|
253
|
-
a = ['<a']
|
254
|
-
a << "href=#{link[:url]}"
|
255
|
-
a << "class=\"#{link[:class]}\"" if link[:class]
|
256
|
-
a << "target=\"_blank\"" if absolute? link[:url]
|
257
|
-
a << '>'
|
258
|
-
a << "<i class=\"fa #{link[:icon]}\"></i>" if link[:icon]
|
259
|
-
a << link[:title]
|
260
|
-
a << '</a>'
|
261
|
-
a.join("\n")
|
262
|
-
end
|
263
|
-
|
264
|
-
# Is the given URI absolute? (or relative?)
|
265
|
-
def absolute?(uri)
|
266
|
-
URI.parse(uri).absolute?
|
267
|
-
end
|
268
|
-
|
269
|
-
# Prettify given data.
|
270
|
-
def prettify(data)
|
271
|
-
return prettify_tuple(data) if tuple? data
|
272
|
-
return prettify_float(data) if data.is_a? Float
|
273
|
-
data
|
274
|
-
end
|
275
|
-
|
276
|
-
# Formats float as "a.bcd" or "a x b^c". The latter if float is
|
277
|
-
# scientific notation. Former otherwise.
|
278
|
-
def prettify_float(float)
|
279
|
-
float.to_s.sub(/(\d*\.\d*)e?([+-]\d*)?/) do
|
280
|
-
base = Regexp.last_match[1]
|
281
|
-
power = Regexp.last_match[2]
|
282
|
-
s = format '%.2f', base
|
283
|
-
s << " × 10<sup>#{power}</sup>" if power
|
284
|
-
s
|
285
|
-
end
|
286
|
-
end
|
287
|
-
|
288
|
-
# Formats an array of two elements as "first (last)".
|
289
|
-
def prettify_tuple(tuple)
|
290
|
-
"#{tuple.first} (#{tuple.last})"
|
291
|
-
end
|
292
|
-
|
293
|
-
# Is the given value a tuple? (array of length two).
|
294
|
-
def tuple?(data)
|
295
|
-
return true if data.is_a?(Array) && data.length == 2
|
296
|
-
end
|
297
|
-
end
|
298
|
-
|
299
|
-
# For any request that hits the app in development mode, log incoming
|
300
|
-
# params.
|
301
|
-
before do
|
302
|
-
logger.debug params
|
303
|
-
end
|
304
|
-
|
305
|
-
# Render the search form.
|
306
|
-
get '/' do
|
307
|
-
erb :search, :locals => { :databases => Database.group_by(&:type) }
|
308
|
-
end
|
309
|
-
|
310
|
-
# BLAST search!
|
311
|
-
post '/' do
|
312
|
-
erb :result, :locals => { :report => BLAST.run(params) }
|
313
|
-
end
|
314
|
-
|
315
|
-
# @params sequence_ids: whitespace separated list of sequence ids to
|
316
|
-
# retrieve
|
317
|
-
# @params database_ids: whitespace separated list of database ids to
|
318
|
-
# retrieve the sequence from.
|
319
|
-
# @params download: whether to return raw response or initiate file
|
320
|
-
# download
|
321
|
-
#
|
322
|
-
# Use whitespace to separate entries in sequence_ids (all other chars exist
|
323
|
-
# in identifiers) and retreival_databases (we don't allow whitespace in a
|
324
|
-
# database's name, so it's safe).
|
325
|
-
get '/get_sequence/' do
|
326
|
-
sequence_ids = params[:sequence_ids].split(/\s/)
|
327
|
-
database_ids = params[:database_ids].split(/\s/)
|
328
|
-
|
329
|
-
sequences = Sequence.from_blastdb(sequence_ids, database_ids)
|
330
|
-
|
331
|
-
if params[:download]
|
332
|
-
file_name = "sequenceserver_#{sequence_ids.first}.fasta"
|
333
|
-
file = Tempfile.new file_name
|
334
|
-
sequences.each do |sequence|
|
335
|
-
file.puts sequence.fasta
|
336
|
-
end
|
337
|
-
file.close
|
338
|
-
send_file file.path, :type => :fasta, :filename => file_name
|
339
|
-
else
|
340
|
-
{
|
341
|
-
:sequence_ids => sequence_ids,
|
342
|
-
:databases => Database[database_ids].map(&:title),
|
343
|
-
:sequences => sequences.map(&:info)
|
344
|
-
}.to_json
|
345
|
-
end
|
346
|
-
end
|
347
|
-
|
348
|
-
get '/get_report/' do
|
349
|
-
ofile = BLAST.format(params)
|
350
|
-
|
351
|
-
send_file ofile[:filepath],
|
352
|
-
:filename => ofile[:filename],
|
353
|
-
:type => ofile[:type].to_sym
|
354
|
-
end
|
355
|
-
|
356
|
-
# This error block will only ever be hit if the user gives us a funny
|
357
|
-
# sequence or incorrect advanced parameter. Well, we could hit this block
|
358
|
-
# if someone is playing around with our HTTP API too.
|
359
|
-
error BLAST::ArgumentError do
|
360
|
-
status 400
|
361
|
-
error = env['sinatra.error']
|
362
|
-
erb :'400', :locals => { :error => error }
|
363
|
-
end
|
364
|
-
|
365
|
-
# This will catch any unhandled error and some very special errors. Ideally
|
366
|
-
# we will never hit this block. If we do, there's a bug in SequenceServer
|
367
|
-
# or something really weird going on. If we hit this error block we show
|
368
|
-
# the stacktrace to the user requesting them to post the same to our Google
|
369
|
-
# Group.
|
370
|
-
error Exception, BLAST::RuntimeError do
|
371
|
-
status 500
|
372
|
-
error = env['sinatra.error']
|
373
|
-
erb :'500', :locals => { :error => error }
|
374
|
-
end
|
375
|
-
end
|
376
202
|
end
|
data/lib/sequenceserver/blast.rb
CHANGED
@@ -5,6 +5,8 @@ require 'ox'
|
|
5
5
|
|
6
6
|
require 'sequenceserver/links'
|
7
7
|
require 'sequenceserver/blast/exceptions'
|
8
|
+
require 'sequenceserver/blast/constants'
|
9
|
+
require 'sequenceserver/blast/formatter'
|
8
10
|
require 'sequenceserver/blast/report'
|
9
11
|
require 'sequenceserver/blast/query'
|
10
12
|
require 'sequenceserver/blast/hit'
|
@@ -16,25 +18,6 @@ module SequenceServer
|
|
16
18
|
# `BLAST::ArgumentError` and `BLAST::RuntimeError` signal errors encountered
|
17
19
|
# when attempting a BLAST search.
|
18
20
|
module BLAST
|
19
|
-
ERROR_LINE = /\(CArgException.*\)\s(.*)/
|
20
|
-
|
21
|
-
ALGORITHMS = %w(blastn blastp blastx tblastn tblastx)
|
22
|
-
|
23
|
-
OUTFMT = {
|
24
|
-
'pairwise' => [0, 'txt'],
|
25
|
-
'qa_identity' => [1, 'txt'],
|
26
|
-
'qa_no_identity' => [2, 'txt'],
|
27
|
-
'fqa_identity' => [3, 'txt'],
|
28
|
-
'fqa_no_identity' => [4, 'txt'],
|
29
|
-
'xml' => [5, 'xml'],
|
30
|
-
'tsv' => [6, 'tsv'],
|
31
|
-
'tsv_commented' => [7, 'tsv'],
|
32
|
-
'asn_text' => [8, 'asn'],
|
33
|
-
'asn_binary' => [9, 'asn'],
|
34
|
-
'csv' => [10, 'csv'],
|
35
|
-
'archive' => [11, 'txt']
|
36
|
-
} # See [1]
|
37
|
-
|
38
21
|
class << self
|
39
22
|
extend Forwardable
|
40
23
|
|
@@ -109,30 +92,8 @@ module SequenceServer
|
|
109
92
|
fail RuntimeError.new(status, error)
|
110
93
|
end
|
111
94
|
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
def format(params)
|
116
|
-
validate_format_params params
|
117
|
-
|
118
|
-
rfile = create_file_path params['report']
|
119
|
-
ofmt = OUTFMT[params['format']]
|
120
|
-
type = params['type']
|
121
|
-
|
122
|
-
oname = "seqserv_#{type}_#{Time.now.strftime('%H%M')}.#{ofmt[1]}"
|
123
|
-
ofile = Tempfile.new oname
|
124
|
-
|
125
|
-
command = "blast_formatter -archive '#{rfile}' -out #{ofile.path}" \
|
126
|
-
" -outfmt '#{ofmt[0]} #{params['specifiers']}' 2> /dev/null"
|
127
|
-
|
128
|
-
logger.debug("Executing: #{command}")
|
129
|
-
system command
|
130
|
-
|
131
|
-
{
|
132
|
-
:filepath => ofile.path,
|
133
|
-
:filename => oname,
|
134
|
-
:type => ofmt[1]
|
135
|
-
}
|
95
|
+
Search << rfile
|
96
|
+
Report.new(File.basename(rfile.path), databases)
|
136
97
|
end
|
137
98
|
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity
|
138
99
|
# rubocop:enable Metrics/MethodLength
|
@@ -141,16 +102,6 @@ module SequenceServer
|
|
141
102
|
params[:sequence].strip! unless params[:sequence].nil?
|
142
103
|
end
|
143
104
|
|
144
|
-
def validate_format_params(params)
|
145
|
-
return true if params.include?('report') &&
|
146
|
-
params.include?('format') &&
|
147
|
-
File.exist?(create_file_path params['report'])
|
148
|
-
fail ArgumentError, <<MSG
|
149
|
-
Incorrect request parameters. Please ensure that requested file name is
|
150
|
-
correct and the file type is either xml or tsv.
|
151
|
-
MSG
|
152
|
-
end
|
153
|
-
|
154
105
|
def validate_blast_params(params)
|
155
106
|
validate_blast_method params[:method]
|
156
107
|
validate_blast_sequences params[:sequence]
|
@@ -201,12 +152,6 @@ MSG
|
|
201
152
|
true
|
202
153
|
end
|
203
154
|
|
204
|
-
# Returns filename if path exists otherwise returns a path to tmp dir.
|
205
|
-
def create_file_path(filename)
|
206
|
-
return File.join(Dir.tmpdir, filename) unless File.exist? filename
|
207
|
-
filename
|
208
|
-
end
|
209
|
-
|
210
155
|
def allowed_chars
|
211
156
|
/\A[a-z0-9\-_\. ']*\Z/i
|
212
157
|
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module SequenceServer
|
2
|
+
# Define constanst used by BLAST module.
|
3
|
+
module BLAST
|
4
|
+
ERROR_LINE = /\(CArgException.*\)\s(.*)/
|
5
|
+
|
6
|
+
ALGORITHMS = %w(blastn blastp blastx tblastn tblastx)
|
7
|
+
|
8
|
+
OUTFMT_SPECIFIERS = %w(qseqid qgi qacc sseqid sallseqid sgi sallgi sacc
|
9
|
+
sallacc qstart qend sstart send qseq sseq evalue
|
10
|
+
bitscore score length length pident nident
|
11
|
+
mismatch positive gapopen gaps ppos frames
|
12
|
+
qframe hframe btop staxids sscinames scomnames
|
13
|
+
sblastnames sskingdoms stitle salltitles sstrand
|
14
|
+
qcovs qcovhsp)
|
15
|
+
OUTFMT = {
|
16
|
+
'pairwise' => [0, :txt],
|
17
|
+
'qa' => [1, :txt],
|
18
|
+
'qa_no_identity' => [2, :txt],
|
19
|
+
'fqa' => [3, :txt],
|
20
|
+
'fqa_no_identity' => [4, :txt],
|
21
|
+
'xml' => [5, :xml],
|
22
|
+
'std_tsv' => [7, :tsv],
|
23
|
+
'full_tsv' => [7, :tsv, OUTFMT_SPECIFIERS],
|
24
|
+
'asn_text' => [8, :asn],
|
25
|
+
'asn_binary' => [9, :asn],
|
26
|
+
'csv' => [10, :csv],
|
27
|
+
'archive' => [11, :txt]
|
28
|
+
}
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# References
|
33
|
+
# ----------
|
34
|
+
# [1]: http://www.ncbi.nlm.nih.gov/books/NBK1763/
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
|
3
|
+
module SequenceServer
|
4
|
+
module BLAST
|
5
|
+
# Formats BLAST+ archive file format into other file formats.
|
6
|
+
class Formatter
|
7
|
+
class << self
|
8
|
+
alias_method :run, :new
|
9
|
+
end
|
10
|
+
|
11
|
+
extend Forwardable
|
12
|
+
|
13
|
+
def_delegators SequenceServer, :logger
|
14
|
+
|
15
|
+
def initialize(search_id, type)
|
16
|
+
@archive_file = get_archive_file search_id
|
17
|
+
@format, @mime, @specifiers = OUTFMT[type]
|
18
|
+
@type = type
|
19
|
+
|
20
|
+
validate && run
|
21
|
+
end
|
22
|
+
|
23
|
+
attr_reader :archive_file, :type
|
24
|
+
|
25
|
+
attr_reader :format, :mime, :specifiers
|
26
|
+
|
27
|
+
def file
|
28
|
+
@file ||= Tempfile.new filename
|
29
|
+
end
|
30
|
+
|
31
|
+
def filename
|
32
|
+
@filename ||=
|
33
|
+
"sequenceserver-#{type}_report.#{mime}"
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def run
|
39
|
+
command =
|
40
|
+
"blast_formatter -archive '#{archive_file}'" \
|
41
|
+
" -outfmt '#{format} #{specifiers}'" \
|
42
|
+
" -out '#{file.path}' 2> /dev/null"
|
43
|
+
logger.debug("Executing: #{command}")
|
44
|
+
system command
|
45
|
+
end
|
46
|
+
|
47
|
+
def validate
|
48
|
+
return true if archive_file && format &&
|
49
|
+
File.exist?(archive_file)
|
50
|
+
fail ArgumentError, <<MSG
|
51
|
+
Incorrect request parameters. Please ensure that requested file name is
|
52
|
+
correct and the file type is either xml or tsv.
|
53
|
+
MSG
|
54
|
+
end
|
55
|
+
|
56
|
+
# Returns filename if path exists otherwise returns a path to tmp dir.
|
57
|
+
def get_archive_file(file)
|
58
|
+
return unless file
|
59
|
+
return file.path if file.respond_to? :path
|
60
|
+
return file if File.exist? file
|
61
|
+
File.join Dir.tmpdir, file
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# References
|
68
|
+
# ----------
|
69
|
+
# [1]: http://www.ncbi.nlm.nih.gov/books/NBK1763/
|